-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathserver-presence.js
151 lines (128 loc) · 4.18 KB
/
server-presence.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* eslint-disable import/no-unresolved */
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
/* eslint-enable import/no-unresolved */
const Servers = new Mongo.Collection('presence:servers');
if (Servers.createIndexAsync) {
try {
Servers.createIndexAsync({ lastPing: 1 }, { expireAfterSeconds: 10 });
Servers.createIndexAsync({ createdAt: -1 });
} catch (e) {
throw new Meteor.Error('Failed to initialize indexes');
}
} else if (Servers.createIndex) {
Servers.createIndex({ lastPing: 1 }, { expireAfterSeconds: 10 });
Servers.createIndex({ createdAt: -1 });
} else {
Servers._ensureIndex({ lastPing: 1 }, { expireAfterSeconds: 10 });
Servers._ensureIndex({ createdAt: -1 });
}
let serverId = null;
let isWatcher = false;
let observeHandle = null;
let exitGracefully = true;
const exitFunctions = [];
/* eslint-disable import/prefer-default-export */
export const ServerPresence = {};
const insert = () => {
const date = new Date();
serverId = Servers.insert({ lastPing: date, createdAt: date });
};
const runCleanupFunctions = (removedServerId) => {
exitFunctions.forEach((exitFunc) => {
exitFunc(removedServerId);
});
};
const setAsWatcher = () => {
isWatcher = true;
Servers.update({ _id: serverId }, { $set: { watcher: true } });
};
const updateWatcher = () => {
const server = Servers.findOne({}, { sort: { createdAt: -1 } });
if (server._id === serverId) {
setAsWatcher();
}
};
const observe = () => {
observeHandle = Servers.find().observe({
removed(document) {
if (document._id === serverId) {
if (!isWatcher) {
Meteor._debug('Server Presence Timeout', 'The server-presence package has detected inconsistent presence state. To avoid inconsistent database state your application is exiting.');
exitGracefully = false;
process.kill(process.pid, 'SIGHUP');
} else {
insert();
}
} else if (isWatcher) {
if (!document.graceful) {
runCleanupFunctions(document._id);
}
} else if (document.watcher) {
if (!document.graceful) {
runCleanupFunctions(document._id);
}
updateWatcher();
}
},
});
};
const checkForWatcher = () => {
const current = Servers.findOne({ watcher: true });
if (current) {
return true;
}
setAsWatcher();
return false;
};
const start = () => {
observe();
Meteor.setInterval(function serverTick() {
Servers.update(serverId, { $set: { lastPing: new Date() } });
return true;
}, 5000);
insert();
// if there isn't any other instance watching and doing cleanup
// then we need to do a full cleanup since this is likely the only instance
if (!checkForWatcher()) {
runCleanupFunctions();
}
};
const exit = () => {
// Call all of our externally supplied exit functions
runCleanupFunctions(serverId);
};
/*
* We have to bind the meteor environment here since process event callbacks
* run outside fibers
*/
const stop = Meteor.bindEnvironment(function boundEnvironment() {
if (exitGracefully) {
Servers.update({ _id: serverId }, { $set: { graceful: true } });
observeHandle.stop();
exit();
}
});
ServerPresence.onCleanup = (cleanupFunction) => {
if (typeof cleanupFunction === 'function') {
exitFunctions.push(cleanupFunction);
} else {
throw new Meteor.Error('Not A Function', 'ServerPresence.onCleanup requires function as parameter');
}
};
ServerPresence.serverId = () => serverId;
Meteor.startup(() => {
start();
});
/*
* Here we are catching signals due to the fact that node (Maybe it's a Meteor issue?) doesn't
* seem to run the exit callbacks except for SIGHUP. Being that SIGTERM is the standard POSIX
* signal sent when a system shuts down, it doesn't make much sense to only run out cleanup on
* HUP signals.
*/
['SIGINT', 'SIGHUP', 'SIGTERM'].forEach((sig) => {
process.once(sig, () => {
stop();
process.kill(process.pid, sig);
});
});