Heartbeats
This commit is contained in:
parent
1b71d1811d
commit
8199917335
|
@ -27,6 +27,7 @@
|
||||||
"@syndicate-lang/driver-http-node": "^0.2.7",
|
"@syndicate-lang/driver-http-node": "^0.2.7",
|
||||||
"@syndicate-lang/driver-mdns": "^0.2.7",
|
"@syndicate-lang/driver-mdns": "^0.2.7",
|
||||||
"@syndicate-lang/driver-streams-node": "^0.2.7",
|
"@syndicate-lang/driver-streams-node": "^0.2.7",
|
||||||
|
"@syndicate-lang/driver-timer": "^0.2.7",
|
||||||
"@syndicate-lang/driver-websocket": "^0.2.7",
|
"@syndicate-lang/driver-websocket": "^0.2.7",
|
||||||
"debug": "^4.1.1"
|
"debug": "^4.1.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,9 +17,11 @@ const {
|
||||||
Add, Del, Msg, Err, End,
|
Add, Del, Msg, Err, End,
|
||||||
Ping, Pong,
|
Ping, Pong,
|
||||||
makeDecoder,
|
makeDecoder,
|
||||||
|
shouldDebugPrint,
|
||||||
} = activate require("./protocol");
|
} = activate require("./protocol");
|
||||||
const P = activate require("./internal_protocol");
|
const P = activate require("./internal_protocol");
|
||||||
const { recorder } = activate require("./turn");
|
const { recorder } = activate require("./turn");
|
||||||
|
const { heartbeat } = activate require("./heartbeat");
|
||||||
|
|
||||||
assertion type WSServer(url, scope) = Symbol.for('server-websocket-connection');
|
assertion type WSServer(url, scope) = Symbol.for('server-websocket-connection');
|
||||||
assertion type Loopback(scope) = Symbol.for('server-loopback-connection');
|
assertion type Loopback(scope) = Symbol.for('server-loopback-connection');
|
||||||
|
@ -39,7 +41,7 @@ Object.assign(module.exports, {
|
||||||
ForceServerDisconnect,
|
ForceServerDisconnect,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function _genericClientSessionFacet(addr, scope, w0, debug) {
|
export function _genericClientSessionFacet(addr, scope, w0, teardown, debug) {
|
||||||
if (debug === void 0) {
|
if (debug === void 0) {
|
||||||
debug = debugFactory('syndicate/server:client:' + genUuid('?'));
|
debug = debugFactory('syndicate/server:client:' + genUuid('?'));
|
||||||
}
|
}
|
||||||
|
@ -48,10 +50,10 @@ export function _genericClientSessionFacet(addr, scope, w0, debug) {
|
||||||
|
|
||||||
on start debug('+', addr.toString(), scope);
|
on start debug('+', addr.toString(), scope);
|
||||||
on stop debug('-', addr.toString(), scope);
|
on stop debug('-', addr.toString(), scope);
|
||||||
on message _ServerPacket(addr, $m) debug('<', m.toString());
|
on message _ServerPacket(addr, $m) if (shouldDebugPrint(m)) debug('<', m.toString());
|
||||||
|
|
||||||
const w = (x) => {
|
const w = (x) => {
|
||||||
debug('>', x.toString());
|
if (shouldDebugPrint(x)) debug('>', x.toString());
|
||||||
w0(x);
|
w0(x);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,6 +98,9 @@ export function _genericClientSessionFacet(addr, scope, w0, debug) {
|
||||||
this.subs = this.subs.remove(spec);
|
this.subs = this.subs.remove(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetHeartbeat = heartbeat(this, ['client', addr, scope], w, teardown);
|
||||||
|
on message _ServerPacket(addr, _) resetHeartbeat();
|
||||||
|
|
||||||
const _instantiate = (m, vs) => Skeleton.instantiateAssertion(FromServer(addr, m.spec), vs);
|
const _instantiate = (m, vs) => Skeleton.instantiateAssertion(FromServer(addr, m.spec), vs);
|
||||||
|
|
||||||
const _lookup = (CTOR, item) => {
|
const _lookup = (CTOR, item) => {
|
||||||
|
@ -140,20 +145,36 @@ spawn named "ServerClientFactory" {
|
||||||
during Observe(ServerConnected($addr)) assert ServerConnection(addr);
|
during Observe(ServerConnected($addr)) assert ServerConnection(addr);
|
||||||
|
|
||||||
during ServerConnection($addr(WSServer($url, $scope))) spawn named ['ServerClient', addr] {
|
during ServerConnection($addr(WSServer($url, $scope))) spawn named ['ServerClient', addr] {
|
||||||
const wsId = genUuid('ws');
|
const reestablish = () => {
|
||||||
const debug = debugFactory('syndicate/server:client:' + wsId);
|
react {
|
||||||
|
const establishingFacet = currentFacet();
|
||||||
|
const wsId = genUuid('ws');
|
||||||
|
|
||||||
during WS.WebSocket(wsId, url, {}) {
|
during WS.WebSocket(wsId, url, {}) {
|
||||||
on message WS.DataIn(wsId, $data) {
|
on message WS.DataIn(wsId, $data) {
|
||||||
if (data instanceof Bytes) send _ServerPacket(addr, makeDecoder(data).next());
|
if (data instanceof Bytes) send _ServerPacket(addr, makeDecoder(data).next());
|
||||||
|
}
|
||||||
|
|
||||||
|
_genericClientSessionFacet.call(
|
||||||
|
this,
|
||||||
|
addr, scope,
|
||||||
|
(x) => { send WS.DataOut(wsId, new Encoder().push(x).contents()); },
|
||||||
|
() => {
|
||||||
|
establishingFacet.stop(() => {
|
||||||
|
// TODO: abstract this into a flush() function somewhere
|
||||||
|
const ACK = Symbol('ACK');
|
||||||
|
react {
|
||||||
|
stop on message ACK reestablish();
|
||||||
|
on start send ACK;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
debugFactory('syndicate/server:client:' + wsId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_genericClientSessionFacet.call(
|
on start reestablish();
|
||||||
this,
|
|
||||||
addr, scope,
|
|
||||||
(x) => { send WS.DataOut(wsId, new Encoder().push(x).contents()); },
|
|
||||||
debug);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
during ServerConnection($addr(Loopback($scope))) spawn named ['ServerClient', addr] {
|
during ServerConnection($addr(Loopback($scope))) spawn named ['ServerClient', addr] {
|
||||||
|
@ -166,6 +187,7 @@ spawn named "ServerClientFactory" {
|
||||||
this,
|
this,
|
||||||
addr, scope,
|
addr, scope,
|
||||||
(x) => { send P.FromPOA(addr, x); },
|
(x) => { send P.FromPOA(addr, x); },
|
||||||
|
() => { throw new Error("Cannot teardown and reset Loopback connection"); },
|
||||||
debug);
|
debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { TimeLaterThan } = activate require("@syndicate-lang/driver-timer");
|
||||||
|
const debug = require('debug')('syndicate/server:heartbeat');
|
||||||
|
const W = require('./protocol');
|
||||||
|
|
||||||
|
const PERIOD = 60 * 1000; // milliseconds
|
||||||
|
const GRACE = 3 * PERIOD;
|
||||||
|
|
||||||
|
function heartbeat(fields, who, sendMessage, teardown) {
|
||||||
|
debug('Configuring heartbeat', who, PERIOD, GRACE);
|
||||||
|
|
||||||
|
const NEXT_PING_TIME = Symbol('NEXT_PING_TIME');
|
||||||
|
const LAST_RECEIVED_TRAFFIC = Symbol('LAST_RECEIVED_TRAFFIC');
|
||||||
|
|
||||||
|
function now() { return (+(new Date())); } // returns milliseconds
|
||||||
|
|
||||||
|
field fields[NEXT_PING_TIME] = 0;
|
||||||
|
field fields[LAST_RECEIVED_TRAFFIC] = now();
|
||||||
|
|
||||||
|
const scheduleNextPing = () => { fields[NEXT_PING_TIME] = now() + PERIOD; };
|
||||||
|
|
||||||
|
on asserted TimeLaterThan(fields[NEXT_PING_TIME]) {
|
||||||
|
scheduleNextPing();
|
||||||
|
sendMessage(W.Ping());
|
||||||
|
}
|
||||||
|
|
||||||
|
on asserted TimeLaterThan(fields[LAST_RECEIVED_TRAFFIC] + GRACE) {
|
||||||
|
debug('Heartbeat timeout', who, GRACE);
|
||||||
|
teardown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
scheduleNextPing();
|
||||||
|
fields[LAST_RECEIVED_TRAFFIC] = now();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(module.exports, {
|
||||||
|
PERIOD,
|
||||||
|
GRACE,
|
||||||
|
heartbeat,
|
||||||
|
});
|
|
@ -29,6 +29,11 @@ function makeDecoder(initialBuffer) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldDebugPrint(m) {
|
||||||
|
// return !(Ping.isClassOf(m) || Pong.isClassOf(m));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(module.exports, {
|
Object.assign(module.exports, {
|
||||||
Connect,
|
Connect,
|
||||||
Turn,
|
Turn,
|
||||||
|
@ -36,4 +41,5 @@ Object.assign(module.exports, {
|
||||||
Add, Del, Msg, Err, End,
|
Add, Del, Msg, Err, End,
|
||||||
Ping, Pong,
|
Ping, Pong,
|
||||||
makeDecoder,
|
makeDecoder,
|
||||||
|
shouldDebugPrint,
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ const P = activate require("./internal_protocol");
|
||||||
const W = activate require("./protocol");
|
const W = activate require("./protocol");
|
||||||
const B = activate require("./buffer");
|
const B = activate require("./buffer");
|
||||||
const { recorder } = activate require("./turn");
|
const { recorder } = activate require("./turn");
|
||||||
|
const { heartbeat } = activate require("./heartbeat");
|
||||||
|
|
||||||
export function websocketServerFacet(reqId) {
|
export function websocketServerFacet(reqId) {
|
||||||
assert P.POA(reqId);
|
assert P.POA(reqId);
|
||||||
|
@ -58,8 +59,8 @@ spawn named '@syndicate-lang/server/server/POAHandler' {
|
||||||
const debug = debugFactory('syndicate/server:server:' + connId.toString());
|
const debug = debugFactory('syndicate/server:server:' + connId.toString());
|
||||||
on start debug('+');
|
on start debug('+');
|
||||||
on stop debug('-');
|
on stop debug('-');
|
||||||
on message P.FromPOA(connId, $m) debug('<', m.toString());
|
on message P.FromPOA(connId, $m) if (W.shouldDebugPrint(m)) debug('<', m.toString());
|
||||||
on message P.ToPOA(connId, $m) debug('>', m.toString());
|
on message P.ToPOA(connId, $m) if (W.shouldDebugPrint(m)) debug('>', m.toString());
|
||||||
|
|
||||||
field this.scope = null;
|
field this.scope = null;
|
||||||
assert P.POAReady(connId);
|
assert P.POAReady(connId);
|
||||||
|
@ -76,6 +77,11 @@ spawn named '@syndicate-lang/server/server/POAHandler' {
|
||||||
const sendToPOA = (m) => { send P.ToPOA(connId, m); };
|
const sendToPOA = (m) => { send P.ToPOA(connId, m); };
|
||||||
const outboundTurn = recorder(this, 'commitNeeded', (items) => sendToPOA(W.Turn(items)));
|
const outboundTurn = recorder(this, 'commitNeeded', (items) => sendToPOA(W.Turn(items)));
|
||||||
|
|
||||||
|
const poaFacet = currentFacet();
|
||||||
|
const resetHeartbeat = heartbeat(this, ['server', connId], sendToPOA, () => {poaFacet.stop();});
|
||||||
|
on message P.FromPOA(connId, _) resetHeartbeat();
|
||||||
|
on message P.FromPOA(connId, W.Ping()) sendToPOA(W.Pong());
|
||||||
|
|
||||||
on message P.FromPOA(connId, W.Turn($items)) {
|
on message P.FromPOA(connId, W.Turn($items)) {
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (W.Assert.isClassOf(item)) {
|
if (W.Assert.isClassOf(item)) {
|
||||||
|
|
Loading…
Reference in New Issue