1
0
mirror of https://github.com/ohwgiles/laminar.git synced 2024-10-27 20:34:20 +00:00

improve websocket handling

The previous implementation meant that messages could get lost if
both sending and receiving were scheduled to be processed in the
same event loop cycle. This commit separates the two channels
more clearly, while still allowing the close event in the receive
side to cancel the whole pipeline

Part of #49 refactor
This commit is contained in:
Oliver Giles 2018-07-06 12:32:20 +03:00
parent 4c2aa2680f
commit 078e0e9882

View File

@ -218,10 +218,9 @@ private:
kj::Own<kj::PromiseFulfiller<void>> fulfiller; kj::Own<kj::PromiseFulfiller<void>> fulfiller;
}; };
kj::Promise<void> handleWebsocket(WebsocketClient& lc) { kj::Promise<void> websocketRead(WebsocketClient& lc)
auto paf = kj::newPromiseAndFulfiller<void>(); {
lc.fulfiller = kj::mv(paf.fulfiller); return lc.ws->receive().then([&lc,this](kj::WebSocket::Message&& message) {
return lc.ws->receive().then([&lc,this](kj::WebSocket::Message&& message) -> kj::Promise<bool> {
KJ_SWITCH_ONEOF(message) { KJ_SWITCH_ONEOF(message) {
KJ_CASE_ONEOF(str, kj::String) { KJ_CASE_ONEOF(str, kj::String) {
rapidjson::Document d; rapidjson::Document d;
@ -230,31 +229,36 @@ private:
int page = d["page"].GetInt(); int page = d["page"].GetInt();
lc.scope.page = page; lc.scope.page = page;
laminar.sendStatus(&lc); laminar.sendStatus(&lc);
// freeze this promise. sendStatus will cause the other half of return websocketRead(lc);
// exclusiveJoin below to proceed and this branch will be cancelled
return kj::NEVER_DONE;
} }
} }
KJ_CASE_ONEOF(close, kj::WebSocket::Close) { KJ_CASE_ONEOF(close, kj::WebSocket::Close) {
// clean socket shutdown // clean socket shutdown
return lc.ws->close(close.code, close.reason).then([]{return false;}); return lc.ws->close(close.code, close.reason);
} }
KJ_CASE_ONEOF_DEFAULT {} KJ_CASE_ONEOF_DEFAULT {}
} }
// unhandled/unknown message // unhandled/unknown message
return lc.ws->disconnect().then([]{return false;}); return lc.ws->disconnect();
}).exclusiveJoin(kj::mv(paf.promise).then([&lc]{ });
}
kj::Promise<void> websocketWrite(WebsocketClient& lc)
{
auto paf = kj::newPromiseAndFulfiller<void>();
lc.fulfiller = kj::mv(paf.fulfiller);
return paf.promise.then([this,&lc]{
kj::Promise<void> p = kj::READY_NOW; kj::Promise<void> p = kj::READY_NOW;
for(std::string& s : lc.messages) { std::list<std::string> messages = kj::mv(lc.messages);
for(std::string& s : messages) {
p = p.then([&s,&lc]{ p = p.then([&s,&lc]{
kj::String str = kj::str(s); kj::String str = kj::str(s);
return lc.ws->send(str).attach(kj::mv(str)); return lc.ws->send(str).attach(kj::mv(str));
}); });
} }
return p.then([]{return true;}); return p.attach(kj::mv(messages)).then([this,&lc]{
//return lc.ws->send(str).attach(kj::mv(str)).then([]{ return true;}); return websocketWrite(lc);
})).then([this,&lc](bool cont){ });
return cont ? handleWebsocket(lc) : kj::READY_NOW;
}); });
} }
@ -285,7 +289,7 @@ private:
} }
} }
laminar.registerClient(&lc); laminar.registerClient(&lc);
kj::Promise<void> connection = handleWebsocket(lc); kj::Promise<void> connection = websocketRead(lc).exclusiveJoin(websocketWrite(lc));
// registerClient can happen after a successful websocket handshake. // registerClient can happen after a successful websocket handshake.
// However, the connection might not be closed gracefully, so the // However, the connection might not be closed gracefully, so the
// corresponding deregister operation happens in the WebsocketClient // corresponding deregister operation happens in the WebsocketClient