-
-
Notifications
You must be signed in to change notification settings - Fork 181
Expand file tree
/
Copy pathmormot.net.async.pas
More file actions
6690 lines (6365 loc) · 250 KB
/
Copy pathmormot.net.async.pas
File metadata and controls
6690 lines (6365 loc) · 250 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// Asynchronous Network Layer for Event-Driven Clients or Servers
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.net.async;
{
*****************************************************************************
Event-Driven Network Classes and Functions
- Low-Level Non-blocking Connections
- Client or Server Asynchronous Process
- THttpAsyncServer Event-Driven HTTP Server
- THttpProxyServer HTTP Server With Proxy and Cache
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
{$ifdef NO_ASYNC_WINIOCP}
{$undef USE_WINIOCP}
{$endif NO_ASYNC_WINIOCP}
// you may define NO_ASYNC_WINIOCP conditional to force regular select() instead
// of TWinIocp - but the later seems faster and should scale much better
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.os.security,
mormot.core.data,
mormot.core.unicode,
mormot.core.text,
mormot.core.buffers,
mormot.core.datetime,
mormot.core.threads,
mormot.core.search,
mormot.core.log,
mormot.core.rtti,
mormot.core.json,
mormot.core.zip,
mormot.crypt.core,
mormot.crypt.secure,
mormot.net.sock,
mormot.net.http,
mormot.net.client,
mormot.net.server; // for multi-threaded process
{ ******************** Low-Level Non-blocking Connections }
type
{$M+}
TPollAsyncSockets = class;
{$M-}
/// define the TPollAsyncSockets.OnRead/AfterWrite method result
// - soContinue should continue reading/writing content from/to the socket
// - soDone should unsubscribe for the current read/write phase, but should
// not shutdown the socket yet
// - soWaitWrite (for AfterWrite) should wait a little then retry writing
// - soClose would shutdown the socket
TPollAsyncSocketOnReadWrite = (
soContinue,
soWaitWrite,
soDone,
soClose
);
/// low-level flags used by the state machine about one TPollAsyncConnection
// - fWasActive is set by TAsyncConnections.IdleEverySecond to purge rd/wr
// unused buffers, to avoid calling GetTickCount64 for every activity
// - fClosed is set by OnClose virtual method
// - fFirstRead is set once TPollAsyncSockets.OnFirstRead is called
// - fSubRead/fSubWrite flags are set when Subscribe() has been called
// - fInList indicates that ConnectionAdd() did register the connection
// - fReadPending states that there is a pending event for this connection
// - note: better keep it up to 8 items to fit in a byte (faster access)
TPollAsyncConnectionFlags = set of (
fWasActive,
fClosed,
fFirstRead,
{$ifndef USE_WINIOCP}
fSubRead,
fSubWrite,
{$endif USE_WINIOCP}
fInList,
fReadPending
);
/// abstract parent to store information about one TPollAsyncSockets connection
TPollAsyncConnection = class(TSynPersistent)
protected
/// the associated TCP connection
// - equals nil after TPollAsyncSockets.Stop
fSocket: TNetSocket;
/// the associated 32-bit sequence number
// - equals 0 after TPollAsyncSockets.Stop
fHandle: TConnectionAsyncHandle;
/// low-level 8-bit flags used by the state machine about this connection
fFlags: TPollAsyncConnectionFlags;
/// internal 8-bit flags e.g. for fRW[] or IOCP or to mark AddGC()
fInternalFlags: set of (
ifWriteWait, ifFromGC, ifInGC, ifSeparateWLock, ifProcessing);
/// the current (reusable) receiving data buffer of this connection
fRd: TRawByteStringBuffer;
/// the current (reusable) sending data buffer of this connection
fWr: TRawByteStringBuffer;
/// re-entrant TryLock/Unlock R/W thread acquisition
// - by default, a single lock is used for all connection access, but
// ifSeparateWLock may be set to separate Recv() and Send()
fRWSafe: array[0..1] of TMultiLightLock;
/// low-level TLS context
fSecure: INetTls;
/// the thread currently set by ProcessRead - maybe nil e.g. on write
fReadThread: TSynThread;
// how many bytes have been transmitted via Send() and Recv() methods
fBytesRecv, fBytesSend: Int64;
{$ifdef USE_WINIOCP}
// opaque Windows IOCP instances returned by TWinIocp.Subscribe()
fIocpSub: PWinIocpSubscription; // from the main fIocpRecvSend IOCP queue
function IocpPrepareNextWrite(queue: TWinIocp): boolean;
{$endif USE_WINIOCP}
/// called when the instance is connected to a poll, after Create or Recycle
// - i.e. at the end of TAsyncConnections.ConnectionNew(), when Handle is set
// - overriding this method is cheaper than its plain Create constructor
// - default implementation does nothing
procedure AfterCreate; virtual;
/// called when the instance is about to be deleted from a poll
// - overriding this method is cheaper than the plain Destroy destructor
// - default implementation does nothing
procedure BeforeDestroy; virtual;
/// called when fFirstRead flag is set, i.e. once just after connection
// - should return true on success, or false to close the connection
// - this default implementation will just call aOwner.fOnFirstRead()
// and return false on any exception (typically a TLS error)
function OnFirstRead(aOwner: TPollAsyncSockets): boolean; virtual;
/// called just before ProcessRead/OnRead are done
// - is overriden e.g. in THttpAsyncConnection to wait for background Write
procedure BeforeProcessRead; virtual;
/// this method is called when the some input data is pending on the socket
// - should extract frames or requests from Connection.rd, and handle them
// - this is where the input should be parsed and extracted according to
// the implemented procotol; Connection.rd could be kept as temporary
// buffer during the parsing, and rd.Reset called once processed
// - Sender.Write() could be used for asynchronous answer sending
// - Sender.LogVerbose() allows logging of escaped data
// - could return sorClose to shutdown the socket, e.g. on parsing error
function OnRead: TPollAsyncSocketOnReadWrite; virtual; abstract;
/// called by TPollAsyncSockets.SubscribeConnection([pseWrite]
// just after fWrite.Subscribe()
procedure OnAfterWriteSubscribe; virtual;
/// this method is called when some data has been written to the socket
// - default implementation will do nothing - see e.g. TRtspConnection
// - you may send data asynchronously using Connection.wr.Append()
function AfterWrite: TPollAsyncSocketOnReadWrite; virtual;
/// this method is called when the sockets is closing
// - overriden e.g. by TWebSocketAsyncConnection to notify the closing
procedure OnClose; virtual;
/// called by ReleaseMemoryOnIdle within the read lock: clean fRd here
function ReleaseReadMemoryOnIdle: PtrInt; virtual;
function ReleaseWriteMemoryOnIdle: PtrInt; virtual;
public
/// inherited classes should never call it, but reintroduce their own Create
// and override AfterCreate if needed
constructor Create; override;
/// finalize the instance
destructor Destroy; override;
/// quick check if this instance seems still active, i.e. its Handle <> 0
function IsDangling: boolean;
{$ifdef HASINLINE} inline; {$endif}
/// quick check if this instance is still open
function IsClosed: boolean;
{$ifdef HASINLINE} inline; {$endif}
/// acquire an exclusive R/W access to this connection
// - returns true if connection has been acquired, setting the wasactive flag
// - returns false if it is used by another thread
function TryLock(writer: boolean): boolean;
/// try to acquire an exclusive R/W access to this connection
// - returns true if connection has been acquired
// - returns false if it is used by another thread, after the timeoutMS period
// - only with writer=true after a locked read + process, so unlikely to sleep
function WaitLock(writer: boolean; timeoutMS: cardinal): boolean;
/// release exclusive R/W access to this connection
procedure UnLock(writer: boolean);
{$ifdef HASINLINE} inline; {$endif}
/// called after TAsyncConnections.LastOperationReleaseMemorySeconds
function ReleaseMemoryOnIdle: PtrInt;
/// send some buffer to the connection, using TLS if possible
// - you should have acquired the instance lock via TryLock/WaitLock(true)
function Send(buf: pointer; var len: integer): TNetResult;
/// receive some buffer from the connection, using TLS if possible
// - you should have acquired the instance lock via TryLock/WaitLock(false)
function Recv(buf: pointer; var len: integer): TNetResult;
/// read-only access to the socket number associated with this connection
property Socket: TNetSocket
read fSocket;
/// read-only access to the low-level TLS context
property Secure: INetTls
read fSecure;
published
/// read-only access to the handle number associated with this connection
property Handle: TConnectionAsyncHandle
read fHandle;
/// how many incoming bytes are currently pending in this connection memory buffer
property PendingRead: PtrInt
read fRd.Len;
/// how many outgoing bytes are currently pending in this connection memory buffer
property PendingWrite: PtrInt
read fWr.Len;
end;
/// thread-safe storage of several connections
// - use e.g. by TPollAsyncSockets.ProcessWaitingWrite or to implement
// generational garbage collector of TAsyncConnection instances
TPollAsyncConnections = record
Safe: TLightLock;
Count: integer; // should be integer, not PtrInt
Items: array of TPollAsyncConnection;
end;
PPollAsyncConnections = ^TPollAsyncConnections;
/// possible options for low-level TPollAsyncSockets process
// - as translated from homonymous high-level acoWritePollOnly/acoWriteNoLoop
// TAsyncConnectionsOptions items
// - paoWritePollOnly will delay TPollAsyncSockets.Write() sending to the
// poll/epoll/iocp subscription pool
// - paoWriteNoLoop will disable socket send() loop until short-write occurs
TPollAsyncSocketsOptions = set of (
paoWritePollOnly,
paoWriteNoLoop
);
/// callback prototype for TPollAsyncSockets.OnStart events
// - should return true if Start() should not subscribe for this connection
TOnPollAsyncFunc = function(Sender: TPollAsyncConnection): boolean of object;
/// callback prototype for TPollAsyncSockets.OnStop events
TOnPollAsyncProc = procedure(Sender: TPollAsyncConnection) of object;
{$ifndef USE_WINIOCP}
TPollReadSockets = class(TPollSockets)
protected
function EnsurePending(tag: TPollSocketTag): boolean; override;
procedure SetPending(tag: TPollSocketTag); override;
function UnsetPending(tag: TPollSocketTag): boolean; override;
end;
TPollWriteSockets = TPollSockets;
{$endif USE_WINIOCP}
{$M+}
/// read/write buffer-oriented process of multiple non-blocking connections
// - to be used e.g. for stream protocols (e.g. WebSockets or IoT communication)
// - assigned sockets will be set in non-blocking mode, so that polling will
// work as expected: you should then never use direclty the socket (e.g. via
// blocking TCrtSocket), but rely on this class for asynchronous process:
// TPollAsyncConnection.OnRead() overriden method will receive all incoming
// data from input buffer, and Write() should be called to add send some data,
// potentially asynchronous with an internal buffer
// - ProcessRead/ProcessWrite methods are to be run for actual communication:
// either you call those methods from multiple threads, or you run them in
// loop from a single thread, then define a TSynThreadPool for running any
// blocking process (e.g. computing requests answers) from OnRead callbacks
TPollAsyncSockets = class
protected
{$ifdef USE_WINIOCP}
fIocpRecvSend: TWinIocp; // process both wieRecv and wieSend notifications
{$else}
fRead: TPollReadSockets;
fWrite: TPollWriteSockets; // separated fWrite
{$endif USE_WINIOCP}
fProcessingRead, fProcessingWrite: integer;
fSendBufferSize: integer; // retrieved at first connection Start()
fReadCount: Int64;
fWriteCount: Int64;
fReadBytes: Int64;
fWriteBytes: Int64;
fDebugLog: TSynLogClass;
fOptions: TPollAsyncSocketsOptions;
fTerminated: boolean;
fReadWaitMs: integer;
fOnStart: TOnPollAsyncFunc;
fOnFirstRead, fOnStop: TOnPollAsyncProc;
fWaitingWrite: TPollAsyncConnections; // to implement soWaitWrite
function GetCount: integer;
procedure DoLog(TextFmt: PUtf8Char; const TextArgs: array of const;
Level: TSynLogLevel = sllTrace);
// pseError: return false to close socket and connection
function OnError(connection: TPollAsyncConnection;
events: TPollSocketEvents): boolean; virtual; abstract;
procedure OnClosed(connection: TPollAsyncConnection); virtual; abstract;
procedure RegisterConnection(connection: TPollAsyncConnection); virtual; abstract;
function SubscribeConnection(const caller: ShortString;
connection: TPollAsyncConnection; sub: TPollSocketEvent): boolean;
procedure CloseConnection(var connection: TPollAsyncConnection;
const caller: ShortString); // set connection:=nil and close+GC
function RawWrite(connection: TPollAsyncConnection;
var data: PByte; var datalen: integer): boolean;
function DoAfterWrite(const caller: ShortString;
connection: TPollAsyncConnection): TPollAsyncSocketOnReadWrite;
procedure ProcessWaitingWrite; // pending soWaitWrite
public
/// initialize the read/write sockets polling
// - fRead and fWrite TPollSocketsBuffer instances will track pseRead or
// pseWrite events, and maintain input and output data buffers
constructor Create(aOptions: TPollAsyncSocketsOptions; aThreadCount: integer); virtual;
/// finalize buffer-oriented sockets polling, and release all used memory
destructor Destroy; override;
/// assign a new connection to the internal reading poll
// - the TSocket handle will be set in non-blocking mode from now on - it
// is not recommended to access it directly any more, but use Write() and
// handle OnRead() callback
// - fRead will poll incoming packets, then call OnRead to handle them,
// or Unsubscribe and delete the socket when pseClosed is notified
// - fWrite will poll for outgoing packets as specified by Write(), then
// send any pending data once the socket is ready
// - any manual call of Start() should ensure the connection is non-blocking
function Start(connection: TPollAsyncConnection): boolean; virtual;
/// remove a connection from the internal poll, and shutdown its socket
// - most of the time, the connection is released by OnClosed when the other
// end shutdown the socket; but you can explicitly call this method when
// the connection (and its socket) is to be shutdown
// - this method won't call OnClosed, since it is initiated by the class
function Stop(connection: TPollAsyncConnection;
const caller: ShortString): boolean; virtual;
/// add some data to the asynchronous output buffer of a given connection
// - this method may block if the connection is currently writing from
// another thread (which is not possible from TPollAsyncSockets.Write),
// up to timeout milliseconds
function Write(connection: TPollAsyncConnection;
data: pointer; datalen: integer; timeout: integer = 5000): boolean; virtual;
/// add some data to the asynchronous output buffer of a given connection
function WriteString(connection: TPollAsyncConnection;
const data: RawByteString; timeout: integer = 5000): boolean;
/// one or several threads should execute this method
// - thread-safe handle of any notified incoming packet
// - return true if something has been read or closed, false to retry later
function ProcessRead(Sender: TSynThread;
const notif: TPollSocketResult): boolean;
/// one thread should execute this method with the proper pseWrite notif
// - thread-safe handle of any outgoing packets
// - sent is the number of bytes already sent from connection.fWr buffer,
// e.g. via TWinIocp.PrepareNext(wieSend)
procedure ProcessWrite(const notif: TPollSocketResult; sent: integer);
/// notify internal socket polls to stop their polling loop ASAP
procedure Terminate(waitforMS: integer);
/// some processing options
property Options: TPollAsyncSocketsOptions
read fOptions write fOptions;
/// event called on Start() method success
// - warning: this callback should be very quick because it is blocking
property OnStart: TOnPollAsyncFunc
read fOnStart write fOnStart;
/// event called on first ProcessRead() on a given connection
// - is assigned e.g. to TAsyncServer.OnFirstReadDoTls to setup the TLS
// in one sub-thread of the thread pool
property OnFirstRead: TOnPollAsyncProc
read fOnFirstRead write fOnFirstRead;
/// event called on Stop() method success
// - warning: this callback should be very quick because it is blocking
property OnStop: TOnPollAsyncProc
read fOnStop write fOnStop;
published
/// how many connections are currently managed by this instance
property Count: integer
read GetCount;
/// how many times data has been received by this instance
property ReadCount: Int64
read fReadCount;
/// how many times data has been sent by this instance
property WriteCount: Int64
read fWriteCount;
/// how many data bytes have been received by this instance
property ReadBytes: Int64
read fReadBytes;
/// how many data bytes have been sent by this instance
property WriteBytes: Int64
read fWriteBytes;
// enable WaitFor() during recv() in ProcessRead
// - may enhance responsiveness especially on HTTP/1.0 connections
// - equals 0 ms by default, but could be tuned e.g. to 50 or 100 if needed
// - use with care: performance degrades with highly concurrent HTTP/1.1
property ReadWaitMs: integer
read fReadWaitMs write fReadWaitMs;
{$ifdef USE_WINIOCP}
/// low-level access to the IOCP polling class used for all events
property IocpRecvSend: TWinIocp
read fIocpRecvSend;
{$else}
/// low-level access to the polling class used for recv() data
property PollRead: TPollReadSockets
read fRead;
/// low-level access to the polling class used for send() data
property PollWrite: TPollWriteSockets
write fWrite;
{$endif USE_WINIOCP}
end;
{$M-}
function ToText(so: TPollAsyncSocketOnReadWrite): PShortString; overload;
{ ******************** Client or Server Asynchronous Process }
type
/// exception associated with TAsyncConnection / TAsyncConnections process
EAsyncConnections = class(ESynException);
TAsyncConnections = class;
/// 32-bit type used to store GetTickCount64 div 1000 values
// - as used e.g. by TAsyncConnection.fLastOperation
TAsyncConnectionSec = type cardinal;
/// abstract class to store one TAsyncConnections connection
// - may implement e.g. WebSockets frames, or IoT binary protocol
// - each connection will be identified by a TConnectionAsyncHandle integer
// - idea is to minimize the resources used per connection, and allow full
// customization of the process by overriding the OnRead virtual method (and,
// if needed, AfterCreate/AfterWrite/BeforeDestroy/OnLastOperationIdle)
TAsyncConnection = class(TPollAsyncConnection)
protected
fLastOperation: TAsyncConnectionSec; // as 32-bit monotonic seconds
fRemoteIP4: cardinal; // may contain cLocalhost32 = 127.0.0.1
fRemoteIP: RawUtf8; // never contains '127.0.0.1'
fOwner: TAsyncConnections;
// called after TAsyncConnections.LastOperationIdleSeconds of no activity
// - Sender.Write() could be used to send e.g. a hearbeat frame
// - should finish quickly and be non-blocking
// - returns true to log notified events, false if nothing happened
function OnLastOperationIdle(nowsec: TAsyncConnectionSec): boolean; virtual;
public
/// initialize this instance
constructor Create(aOwner: TAsyncConnections;
const aRemoteIP: TNetAddr); reintroduce; virtual;
/// reuse this instance for a new incoming connection
procedure Recycle(const aRemoteIP: TNetAddr); virtual;
/// read-only access to the associated connections list
property Owner: TAsyncConnections
read fOwner;
published
/// the associated remote IPv4/IPv6, as text
property RemoteIP: RawUtf8
read fRemoteIP;
end;
PAsyncConnection = ^TAsyncConnection;
/// meta-class of one TAsyncConnections connection
TAsyncConnectionClass = class of TAsyncConnection;
/// used to store a dynamic array of TAsyncConnection
TAsyncConnectionDynArray = array of TAsyncConnection;
/// handle multiple non-blocking connections using TAsyncConnection instances
TAsyncConnectionsSockets = class(TPollAsyncSockets)
protected
fOwner: TAsyncConnections;
function GetTotal: integer;
{$ifdef HASINLINE} inline; {$endif}
procedure RegisterConnection(connection: TPollAsyncConnection); override;
// just log the error, and close connection if acoOnErrorContinue is not set
function OnError(connection: TPollAsyncConnection;
events: TPollSocketEvents): boolean; override;
procedure OnClosed(connection: TPollAsyncConnection); override;
public
/// add some data to the asynchronous output buffer of a given connection
// - this overriden method will also log the write operation if needed
// - can be executed from an TAsyncConnection.OnRead method
function Write(connection: TPollAsyncConnection;
data: pointer; datalen: integer; timeout: integer = 5000): boolean; override;
published
/// how many connections have been handled by the poll, from the beginning
property Total: integer
read GetTotal;
end;
{$ifdef USE_WINIOCP}
/// TAsyncConnectionsThread.Execute will directly call TWinIocp.GetNext()
TAsyncConnectionsThreadProcess = (
atpReadPending);
{$else}
/// define what TAsyncConnectionsThread.Execute should actually do
TAsyncConnectionsThreadProcess = (
atpReadSingle,
atpReadPoll,
atpReadPending
);
TAsyncConnectionsThreadProcesses = set of TAsyncConnectionsThreadProcess;
{$endif USE_WINIOCP}
/// used to implement a thread poll to process TAsyncConnection instances
TAsyncConnectionsThread = class(TLoggedThread)
protected
fOwner: TAsyncConnections;
fProcess: TAsyncConnectionsThreadProcess;
fExecuteState: THttpServerExecuteState;
fWakeUp: set of (wuPossible, wuFromSlowProcess); // for atpReadPending
fIndex: integer;
fCustomObject: TObject;
{$ifndef USE_WINIOCP}
fEvent: TSynEvent;
fThreadPollingLastWakeUpTix: integer;
fThreadPollingLastWakeUpEvents: integer;
function GetNextRead(out notif: TPollSocketResult): boolean;
{$endif USE_WINIOCP}
procedure DoExecute; override;
public
/// initialize the thread
constructor Create(aOwner: TAsyncConnections;
aProcess: TAsyncConnectionsThreadProcess; aIndex: integer); reintroduce;
/// finalize the thread resources
destructor Destroy; override;
/// a TObject instance which will be owned by this thread once assigned
// - Destroy will delete it when needed
// - could be used to maintain some thread-speficic resource, e.g. a raw
// DB connection or a (set of) COM object(s)
property CustomObject: TObject
read fCustomObject write fCustomObject;
published
/// which kind of ProcessRead or ProcessWrite this thread is doing
property Process: TAsyncConnectionsThreadProcess
read fProcess;
/// when used as a thread pool, the number of this thread
property Index: integer
read fIndex;
end;
PAsyncConnectionsThread = ^TAsyncConnectionsThread;
/// low-level options for TAsyncConnections processing
// - TAsyncConnectionsSockets.OnError will shutdown the connection on any error,
// unless acoOnErrorContinue is defined
// - acoNoLogRead and acoNoLogWrite could reduce the log verbosity
// - acoVerboseLog will log transmitted frames content, for debugging purposes
// - acoWritePollOnly and acoWriteNoLoop will be translated into
// paoWritePollOnly/paoWriteNoLoop raw async TPollAsyncSockets options
// - acoDebugReadWriteLog would make low-level send/receive logging
// - acoNoConnectionTrack would force to by-pass the internal Connections list
// if it is not needed - not used by now
// - acoEnableTls flag for TLS support, via Windows SChannel or OpenSSL 1.1/3.x
// - either acoThreadCpuAffinity or acoThreadSocketAffinity could be set: the
// first for thread affinity to one CPU logic core, the 2nd for affinity to
// all logical cores of each CPU HW socket (both exclusive)
// - acoReusePort will set SO_REUSEPORT on POSIX, allowing to bind several
// TAsyncConnections on the same port, either within the same process, or as
// separated processes (e.g. to set process affinity to one CPU HW socket)
// - acoThreadSmooting will change the ThreadPollingWakeup() algorithm to
// focus the process on the first threads of the pool - by design, this
// setting will disable both acoThreadCpuAffinity and acoThreadSocketAffinity
TAsyncConnectionsOptions = set of (
acoOnErrorContinue,
acoNoLogRead,
acoNoLogWrite,
acoVerboseLog,
acoWritePollOnly,
acoDebugReadWriteLog,
acoNoConnectionTrack,
acoEnableTls,
acoThreadCpuAffinity,
acoThreadSocketAffinity,
acoReusePort,
acoThreadSmooting,
acoWriteNoLoop
);
/// dynamic array of TAsyncConnectionsThread instances
TAsyncConnectionsThreads = array of TAsyncConnectionsThread;
/// implements an abstract thread-pooled high-performance TCP clients or server
// - internal TAsyncConnectionsSockets will handle high-performance process
// of a high number of long-living simultaneous connections
// - will use a TAsyncConnection inherited class to maintain connection state
// - don't use this abstract class but either TAsyncServer or TAsyncClient
// - under Linux/POSIX, check your "ulimit -H -n" value: one socket consumes
// two file descriptors: you may better add the following line to your
// /etc/limits.conf or /etc/security/limits.conf system file:
// $ * hard nofile 65535
TAsyncConnections = class(TLoggedThread)
protected
fConnectionClass: TAsyncConnectionClass;
fConnection: TAsyncConnectionDynArray; // sorted by TAsyncConnection.Handle
fSockets: TAsyncConnectionsSockets;
fThreads: TAsyncConnectionsThreads;
fConnectionLock: TRWLock; // write lock/block only on connection add/remove
fConnectionCount: integer; // only subscribed - not just after accept()
fConnectionHigh: integer;
fWakeupSafe: TLightLock; // protect ThreadPollingWakeupLocked
fWakeupOne, fWakeupEvents: cardinal; // CAS counters to wakeup threads
fThreadPoolCount: integer;
fLastConnectionFind: integer;
fLastHandle: integer;
fOptions: TAsyncConnectionsOptions;
fLastOperationSec: TAsyncConnectionSec;
fLastOperationReleaseMemorySeconds: cardinal;
fLastOperationIdleSeconds: cardinal;
fKeepConnectionInstanceMS: cardinal;
fLastOperationMS: Int64; // = GetTickCount64 as set by ProcessIdleTix()
{$ifdef USE_WINIOCP}
// in IOCP mode, Execute does wieSend (and wieAccept for TAsyncServer)
fIocpAccept: TWinIocp; // wieAccept in their own IOCP queue
fIocpAcceptSub: PWinIocpSubscription;
{$else}
fThreadReadPoll: TAsyncConnectionsThread;
fThreadPollingWakeupLoad: integer;
fThreadPollingLastWakeUpTix: integer;
fThreadPollingAwakeCount: integer;
fSocketsEpoll: boolean; // = PollSocketClass.FollowEpoll
{$endif USE_WINIOCP}
/// implement generational garbage collector of TAsyncConnection instances
// - we define two generations: GC #1 has a TTL of KeepConnectionInstanceMS
// (100ms) and are used to avoid GPF or confusion on still active connections;
// GC #2 has a TTL of 10 seconds and will be used by ConnectionCreate to
// recycle e.g. THttpAsyncConnection instances between HTTP/1.0 calls
fGC1, fGC2: TPollAsyncConnections;
fGCLast, fGCTix1, fGCTix2: integer;
fOnIdle: array of TOnPollSocketsIdle;
fThreadClients: record // used by TAsyncClient
Count, Timeout: integer;
Address, Port: RawUtf8;
end;
function AllThreadsStarted: boolean; virtual;
procedure AddGC(aConnection: TPollAsyncConnection; const aContext: ShortString);
procedure DoGC;
procedure FreeGC(var conn: TPollAsyncConnections);
function ConnectionCreate(aSocket: TNetSocket; const aRemoteIp: TNetAddr;
out aConnection: TAsyncConnection): boolean; virtual;
function ConnectionNew(aSocket: TNetSocket; aConnection: TAsyncConnection;
aAddAndSubscribe: boolean = true): boolean; virtual;
function ConnectionDelete(
aConnection: TPollAsyncConnection): boolean; overload; virtual;
function LockedConnectionDelete(
aConnection: TAsyncConnection; aIndex: integer): boolean;
procedure ConnectionAdd(conn: TAsyncConnection);
procedure DoLog(Level: TSynLogLevel; TextFmt: PUtf8Char;
const TextArgs: array of const; Instance: TObject);
procedure ProcessIdleTix(Sender: TObject; NowTix: Int64); virtual;
function ProcessClientStart(Sender: TPollAsyncConnection): boolean;
procedure IdleEverySecond; virtual;
{$ifndef USE_WINIOCP}
procedure ThreadPollingWakeupOne; {$ifdef HASINLINE} inline; {$endif}
procedure ThreadPollingWakeupEvents(Events: integer);
procedure ThreadPollingWakeupLocked;
{$endif USE_WINIOCP}
public
/// initialize the multiple connections
// - don't use this constructor but inherited client/server classes
constructor Create(const OnStart, OnStop: TOnNotifyThread;
aConnectionClass: TAsyncConnectionClass; const ProcessName: RawUtf8;
aLog: TSynLogClass; aOptions: TAsyncConnectionsOptions;
aThreadPoolCount: integer); reintroduce; virtual;
/// shut down the instance, releasing all associated threads and sockets
procedure Shutdown; virtual;
/// shut down and finalize the instance, calling Shutdown
destructor Destroy; override;
/// ensure all threads of the pool is bound to a given CPU core
// - may lower performance, but reduce global consumption
procedure SetCpuAffinity(CpuIndex: integer);
/// ensure all threads of the pool is bound to a given CPU HW socket
// - may enhance performance on multi-socket systems
procedure SetSocketAffinity(SocketIndex: integer);
/// add or remove a callback run from ProcessIdleTix() internal method
// - all callbacks will be triggered once with Sender=nil at shutdown
procedure SetOnIdle(const aOnIdle: TOnPollSocketsIdle; Remove: boolean = false);
/// high-level access to a connection instance, from its handle
// - use efficient O(log(n)) binary search
// - could be executed e.g. from a TAsyncConnection.OnRead method
// - raise an exception if acoNoConnectionTrack option was defined
// - returns nil if the handle was not found
// - returns the maching instance, and caller should release the main lock as:
// ! try ... finally UnLock(aLock); end;
function ConnectionFindAndLock(aHandle: TConnectionAsyncHandle;
aLock: TRWLockContext; aIndex: PInteger = nil): TAsyncConnection;
/// high-level access to a connection instance, from its handle
// - use efficient O(log(n)) binary search
// - this method won't keep the main Lock, but this class will ensure that
// the returned pointer will last for at least 100ms until Free is called
function ConnectionFind(Handle: TConnectionAsyncHandle): TAsyncConnection;
/// high-level access to a connection instance, from its handle
// - use efficient O(log(n)) binary search of a TAsyncConnection instance
// - will also thread-safely attempt to acquire one of the connection's lock
// - returns nil if the handle was not found and acquired within WaitTimeoutMS
function ConnectionFindAndWaitLock(Handle: TConnectionAsyncHandle;
LockWriter: boolean; WaitTimeoutMS: cardinal): pointer;
/// low-level access to a connection instance, from its handle
// - use efficient O(log(n)) binary search, since handles are increasing
// - caller should have called Lock before this method is done
function LockedConnectionSearch(Handle: TConnectionAsyncHandle): TAsyncConnection;
/// remove an handle from the internal list, and close its connection
// - raise an exception if acoNoConnectionTrack option was defined
// - could be executed e.g. from a TAsyncConnection.OnRead method
function ConnectionRemove(Handle: TConnectionAsyncHandle): boolean;
/// call ConnectionRemove unless acoNoConnectionTrack is set
procedure EndConnection(connection: TAsyncConnection);
/// add some data to the asynchronous output buffer of a given connection
// - could be executed e.g. from a TAsyncConnection.OnRead method
function Write(connection: TAsyncConnection; data: pointer; datalen: integer;
timeout: integer = 5000): boolean;
/// add some data to the asynchronous output buffer of a given connection
// - could be executed e.g. from a TAsyncConnection.OnRead method
function WriteString(connection: TAsyncConnection; const data: RawByteString;
timeout: integer = 5000): boolean;
/// low-level method to connect a client to this server
// - is called e.g. from fThreadClients
function ThreadClientsConnect: TAsyncConnection;
/// log some binary data with proper escape as sllTrace
// - can be executed e.g. from an TAsyncConnection.OnRead method as such:
// $ if acoVerboseLog in fOwner.Options then
// $ fOwner.LogVerbose(self, ..., fRd);
procedure LogVerbose(connection: TPollAsyncConnection; const ident: RawUtf8;
const identargs: array of const; const data: TRawByteStringBuffer);
/// the current monotonic time elapsed, evaluated in seconds
// - GetTickSecs value set at most every 500ms by ProcessIdleTix()
property LastOperationSec: TAsyncConnectionSec
read fLastOperationSec;
/// the current monotonic time elapsed, evaluated in milliseconds
// - GetTickCount64 value set at most every 500ms by ProcessIdleTix()
property LastOperationMS: Int64
read fLastOperationMS;
/// allow idle connection to release its internal Connection.rd/wr buffers
// - default is 60 seconds, which is pretty conservative
// - could be tuned in case of high numbers of concurrent connections and
// constrained memory, e.g. with a lower value like 2 seconds
property LastOperationReleaseMemorySeconds: cardinal
read fLastOperationReleaseMemorySeconds write fLastOperationReleaseMemorySeconds;
/// will execute TAsyncConnection.OnLastOperationIdle after an idle period
// - could be used to send heartbeats after read/write inactivity
// - equals 0 (i.e. disabled) by default
property LastOperationIdleSeconds: cardinal
read fLastOperationIdleSeconds write fLastOperationIdleSeconds;
/// how many milliseconds a TAsyncConnection instance is kept alive after closing
// - default is 100 ms before the internal GC calls Free on this instance
property KeepConnectionInstanceMS: cardinal
read fKeepConnectionInstanceMS write fKeepConnectionInstanceMS;
/// allow to customize low-level options for processing
property Options: TAsyncConnectionsOptions
read fOptions write fOptions;
{$ifndef USE_WINIOCP}
// how many events a fast active thread is supposed to handle in its loop
// for the acoThreadSmooting option in ThreadPollingWakeup()
// - will wake up the threads only if the previous seem to be somewhat idle
// - default value is (ThreadPoolCount/CpuCount)*8, with a minimum of 4
property ThreadPollingWakeupLoad: integer
read fThreadPollingWakeupLoad write fThreadPollingWakeupLoad;
{$endif USE_WINIOCP}
/// low-level unsafe direct access to the connection instances
// - ensure this property is used in a thread-safe manner, i.e. calling
// ConnectionFindAndLock() high-level function, ot via manual
// ! ConnectionLock.ReadOnlyLock;
// ! try ... finally ConnectionLock.ReadOnlyUnLock; end;
property Connection: TAsyncConnectionDynArray
read fConnection;
/// access to the R/W lock protecting the Connection[] array
// - will WriteLock/block only on connection add/remove
property ConnectionLock: TRWLock
read fConnectionLock;
/// direct access to the class instantiated for each connection
// - as supplied to the constructor, but may be overriden just after startup
property ConnectionClass: TAsyncConnectionClass
read fConnectionClass write fConnectionClass;
/// direct access to the internal AsyncConnectionsThread's
property Threads: TAsyncConnectionsThreads
read fThreads;
published
/// how many read threads there are in this thread pool
property ThreadPoolCount: integer
read fThreadPoolCount;
/// current HTTP/1.1 / WebSockets connections count
// - this is the number of long-living connections - may not appear just
// after accept, so never for a HTTP/1.0 short-living request
property ConnectionCount: integer
read fConnectionCount;
/// maximum number of concurrent long-living connections since started
property ConnectionHigh: integer
read fConnectionHigh;
/// access to the TCP client sockets poll
// - TAsyncConnection.OnRead should rather use Write() and LogVerbose()
// methods of this TAsyncConnections class instead of using Clients
property Sockets: TAsyncConnectionsSockets
read fSockets;
end;
/// implements a thread-pooled high-performance TCP server
// - will use a TAsyncConnection inherited class to maintain connection
// state for server process
TAsyncServer = class(TAsyncConnections)
protected
fServer: TCrtSocket; // for proper complex binding (including TLS)
fMaxPending: integer;
fMaxConnections: integer;
fAccepted: Int64;
fExecuteState: THttpServerExecuteState;
fExecuteAcceptOnly: boolean; // W in other thread (POSIX THttpAsyncServer)
fExecuteMessage: RawUtf8;
fSockPort: RawUtf8;
fBanned: THttpAcceptBan; // for hsoBan40xIP or BlackList
procedure OnFirstReadDoTls(Sender: TPollAsyncConnection);
procedure SetExecuteState(State: THttpServerExecuteState); virtual;
procedure DoExecute; override;
public
/// run the TCP server, listening on a supplied IP port
// - aThreadPoolCount = 1 is fine if the process is almost non-blocking,
// like our mormot.net.rtsphttp relay - but not e.g. for a REST/SOA server
// - with aThreadPoolCount > 1, a thread will do atpReadPoll, and all other
// threads will do atpReadPending for socket reading and processing the data
// - there will always be two other threads, one for Accept() and another
// for asynchronous data writing (i.e. sending to the socket)
// - warning: should call WaitStarted() to let Execute bind and run
// - for TLS support, set acoEnableTls, and once WaitStarted() returned,
// set Server.TLS.CertificateFile/PrivateKeyFile/PrivatePassword properties
// and call Server.DoTlsAfter(cstaBind)
constructor Create(const aPort: RawUtf8;
const OnStart, OnStop: TOnNotifyThread;
aConnectionClass: TAsyncConnectionClass; const ProcessName: RawUtf8;
aLog: TSynLogClass; aOptions: TAsyncConnectionsOptions;
aThreadPoolCount: integer); reintroduce; virtual;
/// to be called just after Create to wait for Execute to Bind
// - will raise an exception on timeout, or if the binding failed
// - needed only for raw protocol implementation: THttpServerGeneric will
// have its own WaitStarted method
procedure WaitStarted(seconds: integer);
/// prepare the server finalization
procedure Shutdown; override;
/// shut down the server, releasing all associated threads and sockets
destructor Destroy; override;
published
/// access to the TCP server socket
property Server: TCrtSocket
read fServer;
/// how many connections have been accepted since server startup
// - ConnectionCount is the number of long-living connections, this
// counter is the absolute number of successful accept() calls,
// including short-living (e.g. HTTP/1.0) connections
property Accepted: Int64
read fAccepted;
/// above how many active connections accept() would reject
// - MaxPending applies to the actual thread-pool processing activity,
// whereas MaxConnections tracks the number of connections even in idle state
property MaxConnections: integer
read fMaxConnections write fMaxConnections;
/// above how many fSockets.fRead.PendingCount accept() would reject
// - is mapped by the high-level THttpAsyncServer.HttpQueueLength property
// - default is 10000, but could be a lower value e.g. for a load-balancer
// - MaxConnections regulates the absolute number of (idle) connections,
// whereas this property tracks the actual REST/HTTP requests pending for
// the internal thread pool
property MaxPending: integer
read fMaxPending write fMaxPending;
end;
/// implements thread-pooled high-performance TCP multiple clients
// - e.g. to run some load stress tests with optimized resource use
// - will use a TAsyncConnection inherited class to maintain connection state
// of each connected client
TAsyncClient = class(TAsyncConnections)
protected
procedure DoExecute; override;
public
/// start the TCP client connections, connecting to the supplied IP server
constructor Create(const aServer, aPort: RawUtf8;
aClientsCount, aClientsTimeoutSecs: integer;
const OnStart, OnStop: TOnNotifyThread;
aConnectionClass: TAsyncConnectionClass; const ProcessName: RawUtf8;
aLog: TSynLogClass; aOptions: TAsyncConnectionsOptions;
aThreadPoolCount: integer = 1); reintroduce; virtual;
published
/// server IP address
property Server: RawUtf8
read fThreadClients.Address;
/// server IP port
property Port: RawUtf8
read fThreadClients.Port;
end;
const
/// the TAsyncConnectionsOptions for THttpAsyncServer running on production
// - with low verbosity of the logs - similar to a plain THttpServer
ASYNC_OPTION_PROD = [
acoNoLogRead,
acoNoLogWrite];
/// the TAsyncConnectionsOptions for debugging THttpAsyncServer
// - with high-level receive/send block information
ASYNC_OPTION_DEBUG = [
];
/// the TAsyncConnectionsOptions for fully detailed debug of THttpAsyncServer
// - with all possible - and very verbose - log information
// - could be used to track performance or heisenbug issues
ASYNC_OPTION_VERBOSE = [
acoVerboseLog,
acoDebugReadWriteLog];
{ ******************** THttpAsyncServer Event-Driven HTTP Server }
type
/// exception associated with Event-Driven HTTP Server process
EHttpAsyncConnections = class(EAsyncConnections);
THttpAsyncClientConnection = class;
THttpAsyncServer = class;
THttpAsyncConnections = class;
/// abstract HTTP server or client connection to our non-blocking THttpAsyncServer
THttpAsyncConnection = class(TAsyncConnection)
protected
fHttp: THttpRequestContext; // non-blocking HTTP state machine
fServer: THttpAsyncServer;
function ReleaseReadMemoryOnIdle: PtrInt; override;
procedure OnAfterWriteSubscribe; override;
public
/// low-level access to the associated HTTP async server instance
property Server: THttpAsyncServer
read fServer;
end;
/// define the TOnHttpClientAsync callback state machine steps
// - hcsBeforeTlsHandshake allows to change connection.Tls parameters
// - hcsAfterTlsHandshake can validate the connection.Tls information
// - hcsBeforeSendHeaders allows to change emitted connection.Http.Head
// - hcsAfterSendHeaders is called just before read subscription
// - hcsReadStateChanged is called by connection.OnRead when Http.State changed
// - hcsHeadersReceived is called when response has set connection.ResponseStatus
// and connection.Http.Headers have been set
// - hcsFinished is called when a response was received, maybe with a body
// - hcsFailed is set on eventual error
TOnHttpClientState = (
hcsBeforeTlsHandshake,
hcsAfterTlsHandshake,
hcsBeforeSendHeaders,
hcsAfterSendHeaders,
hcsReadStateChanged,
hcsHeadersReceived,
hcsFinished,
hcsFailed);
/// define when the TOnHttpClientAsync callback is to be executed
TOnHttpClientStates = set of TOnHttpClientState;
/// callback used e.g. by THttpAsyncClientConnection.OnStateChanged
// - should return soContinue on success, or anything else to abort/close
// - eventually hrsResponseDone or one hrsError* will mark the end of process
TOnHttpClientAsync = function(Sender: TObject; State: TOnHttpClientState;
Connection: THttpAsyncClientConnection): TPollAsyncSocketOnReadWrite of object;
/// handle one HTTP client connection handled by our non-blocking THttpAsyncServer
// - used e.g. for efficient reverse proxy support with another server
THttpAsyncClientConnection = class(THttpAsyncConnection)
protected
fOnStateChanged: TOnHttpClientAsync;
fResponseStatus: integer;
fOnStateChange: TOnHttpClientStates;
fTls: TNetTlsContext;
fSender: TObject;
procedure AfterCreate; override;
procedure BeforeDestroy; override;
function OnRead: TPollAsyncSocketOnReadWrite; override;
function AfterWrite: TPollAsyncSocketOnReadWrite; override;
function NotifyStateChange(state: TOnHttpClientState): TPollAsyncSocketOnReadWrite;
{$ifdef HASINLINE} inline; {$endif}
public
/// access to the associated progress event callback
property OnStateChanged: TOnHttpClientAsync
read fOnStateChanged;
/// server response HTTP status code (e.g. 200)
property ResponseStatus: integer
read fResponseStatus;
/// associated TLS options and informations
property Tls: TNetTlsContext
read fTls write fTls;
end;
PHttpAsyncClientConnection = ^THttpAsyncClientConnection;
/// handle one HTTP server connection to our non-blocking THttpAsyncServer
THttpAsyncServerConnection = class(THttpAsyncConnection)
protected
fKeepAliveMaxSec: TAsyncConnectionSec; // 0 for no keep-alive (force close)
fHeadersSec: TAsyncConnectionSec;
fRequestFlags: THttpServerRequestFlags;
fPipelineState: set of (pEnabled, pWrite);
fRespStatus: cardinal;
fRequest: THttpServerRequest; // recycled between calls
fConnectionOpaque: THttpServerConnectionOpaque; // two PtrUInt tags
fConnectionID: THttpServerConnectionID; // may be <> fHandle behind nginx
fAfterResponseStart: Int64;
fAuthRejectSec: cardinal;
procedure AfterCreate; override;
procedure BeforeDestroy; override;
procedure HttpInit;
{$ifdef HASINLINE} inline; {$endif}
// overriden to wait for background Write to finish
procedure BeforeProcessRead; override;
// redirect to fHttp.ProcessRead()
function OnRead: TPollAsyncSocketOnReadWrite; override;
// redirect to fHttp.ProcessWrite()
function AfterWrite: TPollAsyncSocketOnReadWrite; override;