TsgcWebSocketHTTPServer › Events › OnQueueDrained
Fires when the outbound queue of a WebSocket connection goes from holding messages to being empty.
property OnQueueDrained: TsgcWSQueueDrainedEvent;
// TsgcWSQueueDrainedEvent = procedure(Connection: TsgcWSConnection) of object
—
Only fires when message queuing is active for the affected connection, that is when QueueOptions.Text.Level, QueueOptions.Binary.Level or QueueOptions.Ping.Level is set to something other than qmNone. Without a queue there is nothing to drain and the event is never raised in the default qdmTransition mode. Plain HTTP requests are not queued, so only WebSocket connections raise it.
The handler runs inline on the connection thread and is deliberately not marshalled through NotifyEvents, unlike every other event of this component, so it must not touch the user interface. The deviation is intentional. The whole value of the event is that it runs at that exact point of the connection loop, right after the queue has drained and before the next read, while the socket is still idle, so the application can hand out the next credit without waiting. Marshalling it to the main thread would destroy that. Any form, grid or label update has to be marshalled by the application itself, with TThread.Queue, TThread.Synchronize or a message posted to the main thread.
Exceptions raised inside the handler are caught and discarded so that the drain always completes. A handler that fails does so silently, so wrap the body in its own try..except block when a failure has to be reported.
The QueueOptions.DrainedMode property selects when the event is raised. With qdmTransition (default) it fires once per transition from non-empty to empty, as described below. With qdmEveryPass it fires on every pass of the connection loop (about every Options.ReadTimeout milliseconds), right after the queues are sent, even when the queue was already empty, and even when Text.Level, Binary.Level and Ping.Level are all qmNone, so no dummy queue level is needed. In this mode the handler is a per-connection hook on the connection thread, for example to send pending application data with WriteData so that every write to a connection happens from its own thread (with Binary.Level set to qmNone, WriteData inside the event writes directly to the socket). Like the transition mode it runs inline and does not follow NotifyEvents, so the handler must be fast and must not touch the user interface. Only the default IOHandler (thread per connection) has this loop, with iohIOCP and iohEPOLL the event is never raised per pass. Each connection copies the queue options when it connects, so set DrainedMode before activating the component, connections already open keep the previous mode.
In qdmTransition mode the event fires once per transition from non-empty to empty, not once per pass of the connection loop. It is not a signal that a burst has finished. If the drain keeps pace with a slow producer, every single message can raise its own event, because each message is written out before the next one is enqueued. What the event states is that the queue for this connection is empty right now, nothing more.
The typical use is flow control. When the server relays bulk data between clients, use the event to tell a sending client that it may send more. Pair it with the PendingCount property of TsgcWSConnection, which reports how many messages are still queued for that connection, to run a credit window instead of a stop and wait exchange.
The managed .NET port raises the same event with the same meaning. The one difference is how often it arrives. The Delphi drain runs once per pass of the connection loop, which is Options.ReadTimeOut milliseconds apart and 10 by default, so a slow trickle of messages is coalesced into few events. The managed drain is event driven and the same trickle can raise more events. Bursts behave identically on both.
procedure OnQueueDrained(Connection: TsgcWSConnection);
begin
// runs on the connection thread, do not touch the user interface here
Connection.WriteData('credit 64');
end;
// qdmEveryPass: runs on every pass of the connection loop
oServer.QueueOptions.DrainedMode := qdmEveryPass;
oServer.QueueOptions.Ping.Level := qmLevel1;
procedure OnQueueDrained(Connection: TsgcWSConnection);
begin
// runs on the connection thread, do not touch the user interface here
if Connection.Disconnected then
exit;
if HasPendingData then
Connection.WriteData(GetPendingData);
end;