2018-11-26 13:10:43 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// @syndicate-lang/driver-streams-node, Stream support for Syndicate/js
|
|
|
|
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
import { currentFacet, Observe, Dataspace, genUuid, Bytes } from "@syndicate-lang/core";
|
|
|
|
const S = activate require("./streams");
|
|
|
|
const net = require('net');
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
assertion type TcpAddress(host, port);
|
|
|
|
assertion type TcpListener(port);
|
|
|
|
export { TcpAddress, TcpListener };
|
|
|
|
|
|
|
|
assertion type UnixSocketClient(path);
|
|
|
|
assertion type UnixSocketServer(path);
|
|
|
|
export { UnixSocketClient, UnixSocketServer };
|
|
|
|
|
|
|
|
spawn named 'NetDriver' {
|
2019-05-31 12:58:04 +00:00
|
|
|
during Observe(S.Stream(_, S.Incoming(TcpListener($port)))) spawn named ['TcpListener', port] {
|
2018-11-26 13:10:43 +00:00
|
|
|
_netListener.call(this,
|
|
|
|
() => genUuid('tcp' + port),
|
|
|
|
TcpListener(port),
|
|
|
|
(server) => { server.listen(port, '0.0.0.0') },
|
|
|
|
(server, err) => { throw err; });
|
|
|
|
}
|
|
|
|
|
2019-05-31 12:58:04 +00:00
|
|
|
during Observe(S.Stream(_, S.Incoming(UnixSocketServer($path))))
|
2018-11-26 13:10:43 +00:00
|
|
|
spawn named ['UnixSocketServer', path] {
|
|
|
|
let retried = false;
|
|
|
|
_netListener.call(this,
|
|
|
|
() => genUuid('unix:' + path),
|
|
|
|
UnixSocketServer(path),
|
|
|
|
(server) => { server.listen(path) },
|
|
|
|
(server, err) => {
|
|
|
|
if (err.code === 'EADDRINUSE') {
|
|
|
|
// Potentially-stale socket file sitting around. Try
|
|
|
|
// connecting to it to see if it is alive, and remove it if
|
|
|
|
// not.
|
|
|
|
if (retried) {
|
|
|
|
// We're on our second go already, give up.
|
|
|
|
throw err;
|
|
|
|
} else {
|
|
|
|
retried = true;
|
|
|
|
const probe = new net.Socket();
|
2018-12-12 17:12:15 +00:00
|
|
|
function destroyProbe() {
|
|
|
|
try { probe.destroy() } catch (e) { console.error(e); }
|
|
|
|
}
|
2018-11-26 13:10:43 +00:00
|
|
|
probe.on('error', Dataspace.wrapExternal((e) => {
|
2018-12-12 17:12:15 +00:00
|
|
|
destroyProbe();
|
2018-11-26 13:10:43 +00:00
|
|
|
if (e.code === 'ECONNREFUSED') {
|
|
|
|
fs.unlinkSync(path);
|
|
|
|
server.listen(path);
|
|
|
|
} else {
|
|
|
|
// Something else went wrong! Give up the original listen.
|
|
|
|
console.error('Problem while probing potentially-stale socket', e);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}));
|
2018-12-12 17:12:15 +00:00
|
|
|
probe.connect(path, Dataspace.wrapExternal(() => {
|
|
|
|
destroyProbe();
|
2018-11-26 13:10:43 +00:00
|
|
|
throw err;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function _netListener(idGenerator, spec, listenFun, errorHandler) {
|
|
|
|
let finish = Dataspace.backgroundTask();
|
|
|
|
on stop finish();
|
|
|
|
|
|
|
|
let server = net.createServer(Dataspace.wrapExternal((socket) => {
|
|
|
|
S.spawnConnection(idGenerator(), spec, socket);
|
|
|
|
}));
|
|
|
|
|
|
|
|
server.on('error', Dataspace.wrapExternal((err) => errorHandler(server, err)));
|
|
|
|
listenFun(server);
|
|
|
|
on stop try { server.close() } catch (e) { console.error(e); }
|
|
|
|
}
|
|
|
|
|
2019-05-31 12:58:04 +00:00
|
|
|
during S.Stream($id, S.Outgoing(TcpAddress($host, $port))) spawn named ['Tcp', id, host, port] {
|
2018-11-26 13:10:43 +00:00
|
|
|
_netConnector.call(this,
|
|
|
|
id,
|
|
|
|
(socket) => { socket.connect(port, host) },
|
|
|
|
TcpAddress(host, port));
|
|
|
|
}
|
|
|
|
|
2019-05-31 12:58:04 +00:00
|
|
|
during S.Stream($id, S.Outgoing(UnixSocketClient($path))) spawn named ['Unix', id, path] {
|
2018-11-26 13:10:43 +00:00
|
|
|
_netConnector.call(this,
|
|
|
|
id,
|
|
|
|
(socket) => { socket.connect(path) },
|
|
|
|
UnixSocketClient(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
function _netConnector(id, connectFun, spec) {
|
|
|
|
const establishingFacet = currentFacet();
|
|
|
|
let finish = Dataspace.backgroundTask();
|
|
|
|
|
|
|
|
const socket = new net.Socket();
|
|
|
|
|
|
|
|
const connectionErrorHandler = Dataspace.wrapExternal((err) => {
|
|
|
|
finish();
|
|
|
|
establishingFacet.stop(() => {
|
|
|
|
socket.destroy();
|
2019-05-31 12:58:04 +00:00
|
|
|
send S.Stream(id, S.Rejected(err));
|
2018-11-26 13:10:43 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-05-31 12:58:04 +00:00
|
|
|
on retracted S.Stream(id, S.Outgoing(spec)) {
|
2018-11-26 13:10:43 +00:00
|
|
|
connectionErrorHandler(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
on start {
|
|
|
|
const readyHandler = Dataspace.wrapExternal(() => {
|
|
|
|
socket.off('error', connectionErrorHandler);
|
|
|
|
socket.off('ready', readyHandler);
|
2019-05-31 12:58:04 +00:00
|
|
|
send S.Stream(id, S.Accepted());
|
2018-11-26 13:10:43 +00:00
|
|
|
establishingFacet.stop(() => {
|
|
|
|
react {
|
|
|
|
on stop finish();
|
|
|
|
S.duplexStreamBehaviour(id, socket);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
socket.on('error', connectionErrorHandler);
|
|
|
|
socket.on('ready', readyHandler);
|
|
|
|
connectFun(socket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|