Proper topicTree integration

This commit is contained in:
Alex Hultman
2021-01-29 13:10:49 +01:00
parent dc423cfda2
commit f8800d3e09
+48 -22
View File
@@ -78,16 +78,30 @@ public:
WebSocketContextData() : topicTree([this](Subscriber *s, Intersection &intersection) -> int { WebSocketContextData() : topicTree([this](Subscriber *s, Intersection &intersection) -> int {
/* We could potentially be called here even if we have nothing to send, since we can
std::pair<std::string_view, std::string_view> data = intersection.dataChannels; * be the sender of every single message in this intersection. Also "fin" of a segment is not
* guaranteed to be set, in case remaining segments are all from us.
* Essentially, we cannot make strict assumptions here. Also, we can even come here corked,
* since publish can call drain! */
/* We rely on writing to regular asyncSockets */ /* We rely on writing to regular asyncSockets */
auto *asyncSocket = (AsyncSocket<SSL> *) s->user; auto *asyncSocket = (AsyncSocket<SSL> *) s->user;
/* Check if we now have too much backpressure (todo: don't buffer up before check) */ /* If we are corked, do not uncork - otherwise if we cork in here, uncork before leaving */
if (!maxBackpressure || (unsigned int) asyncSocket->getBufferedAmount() < maxBackpressure) { bool wasCorked = asyncSocket->isCorked();
/* Do we even have room for potential data? */
if (!maxBackpressure || asyncSocket->getBufferedAmount() < maxBackpressure) {
/* Roll over all our segments */
intersection.forSubscriber(topicTree.getSenderFor(s), [asyncSocket, this](std::pair<std::string_view, std::string_view> data, bool fin) {
/* We have a segment that is not marked as last ("fin").
* Cork if not already so (purely for performance reasons). Does not touch "wasCorked". */
if (!fin && !asyncSocket->isCorked() && asyncSocket->canCork()) {
asyncSocket->cork();
}
/* Pick uncompressed data track */ /* Pick uncompressed data track */
std::string_view selectedData = data.first; std::string_view selectedData = data.first;
@@ -103,11 +117,11 @@ public:
WebSocket<SSL, true> *ws = (WebSocket<SSL, true> *) asyncSocket; WebSocket<SSL, true> *ws = (WebSocket<SSL, true> *) asyncSocket;
/* We need to handle being corked, and corking here */ /* For performance reasons we always cork when in dedicated mode.
bool needsUncorking = false; * Is this really the best? We already kind of cork things in Zlib?
* Right, formatting needs a cork buffer, right. Never mind. */
if (!ws->isCorked() && ws->canCork()) { if (!ws->isCorked() && ws->canCork()) {
asyncSocket->cork(); asyncSocket->cork();
needsUncorking = true;
} }
while (selectedData.length()) { while (selectedData.length()) {
@@ -128,33 +142,45 @@ public:
selectedData.remove_prefix(sizeof(MessageMetadata) + mm.length); selectedData.remove_prefix(sizeof(MessageMetadata) + mm.length);
} }
/* Here we need to uncork or keep it as was */ /* Continue to next segment without executing below path */
if (needsUncorking) { return;
asyncSocket->uncork(); }
} }
/* See below */ /* Common path for SHARED and DISABLED. It is an invalid assumption that we always are
return 0; * uncorked here, however the following (invalid) assumption is not critically wrong either way */
}
}
/* Note: this assumes we are not corked, as corking will swallow things and fail later on */ /* Note: this assumes we are not corked, as corking will swallow things and fail later on */
auto [written, failed] = asyncSocket->write(selectedData.data(), (int) selectedData.length()); auto [written, failed] = asyncSocket->write(selectedData.data(), (int) selectedData.length());
/* If we want strict check for success, we can ignore this check if corked and repeat below
* when uncorking - however this is too strict as we really care about PROGRESS rather than
* ENTIRE SUCCESS - we need minor API changes to support correct checks */
if (!failed) { if (!failed) {
if (this->resetIdleTimeoutOnSend) { if (this->resetIdleTimeoutOnSend) {
asyncSocket->timeout(this->idleTimeout); asyncSocket->timeout(this->idleTimeout);
} }
} }
});
/* Failing here must not immediately close the socket, as that could result in stack overflow,
* iterator invalidation and other TopicTree::drain bugs. We may shutdown the reading side of the socket,
* causing next iteration to error-close the socket from that context instead, if we want to */
} }
/* If we have too much backpressure, simply skip sending from here */ /* We are done sending, for whatever reasons we ended up corked while not starting with "wasCorked",
* we here need to uncork to restore the state we were called in */
if (!wasCorked && asyncSocket->isCorked()) {
/* Regarding timeout for writes; */
auto [written, failed] = asyncSocket->uncork();
/* Again, this check should be more like DID WE PROGRESS rather than DID WE SUCCEED ENTIRELY */
if (!failed) {
if (this->resetIdleTimeoutOnSend) {
asyncSocket->timeout(this->idleTimeout);
}
}
}
/* Also (defer) a close if we have too much backpressure if that is what we want */ /* Defer a close if we now have (or already had) too much backpressure, or simply skip */
if (maxBackpressure && closeOnBackpressureLimit && asyncSocket->getBufferedAmount() > maxBackpressure) { if (maxBackpressure && closeOnBackpressureLimit && asyncSocket->getBufferedAmount() > maxBackpressure) {
/* We must not immediately close the socket, as that could result in stack overflow,
* iterator invalidation and other TopicTree::drain bugs. We may shutdown the reading side of the socket,
* causing next iteration to error-close the socket from that context instead, if we want to */
us_socket_shutdown_read(SSL, (us_socket_t *) asyncSocket); us_socket_shutdown_read(SSL, (us_socket_t *) asyncSocket);
} }