-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathP2PeerHub.cpp
More file actions
2924 lines (2719 loc) · 116 KB
/
Copy pathP2PeerHub.cpp
File metadata and controls
2924 lines (2719 loc) · 116 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
// Copyright © 2001-2012, 2026 Ivyware Pty Ltd, Khrustal & Mann
// MELBOURNE, VICTORIA, AUSTRALIA, 3000
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
//
// P2PeerHub definitions and prototypes
// NOTES: Network is built upon the local and remote exchange of
// P2PeerMsg's between objects derived from this base class
// : Only P2PeerHub derived network objects may be assigned
// network P2Paddress's
// : Network connections between P2PeeHub's are managed via
// pumped P2PeerCon objects
#include "stdafx.h"
#include "Kernel32_Ext.h"
#include "P2PeerHub.h"
#include "P2PeerConWsa.h"
#include "P2Pwin32.h"
#include "Msgexception.h"
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <string> // ProvisionAuth builds the ".pub" sibling path (Stage 3 step 8)
// W8 (p2p_PumpPerf.md — deployment tuning): the per-hub pump-slot cap was hard-coded (15) in the
// SpawnHub convenience path. Expose it as an additive env knob (P2PMSG_PUMPS_MAX) so a deployment
// can raise the ceiling without recompiling; the CreateHub() integration path already takes
// nPumpsMax as a parameter, so this only closes the gap for SpawnHub. UNSET ⇒ the historical 15,
// so existing behaviour is byte-identical (no test/perf change). The value is resolved once via a
// C++11 magic-static (guard-protected init ⇒ TSan-clean under concurrent SpawnHub calls; the
// racy lazy `if(!x)` idiom would trip the TSan gate). Clamp to [15, MAX_P2PmsgPump]: the floor
// keeps it PURELY additive (a knob can only RAISE the cap, never regress a hub that legitimately
// fills the historical 15 pump slots); the ceiling is the library's own hard limit —
// CreateP2PmsgHub throws "nMaxPumps outside range 1..MAX_P2PmsgPump" (P2Pwin32.cpp), so a value
// above it would fail SpawnHub outright (measured: cap 512 ⇒ "server SpawnHub failed"). See
// PumpDeploymentTuning.md for the sizing/affinity/wait-strategy guidance this pairs with.
static UINT HubPumpsMaxKnob ( )
{
static const UINT s_nPumpsMax = [] () -> UINT {
#if defined(_MSC_VER)
# pragma warning( push )
# pragma warning( disable : 4996 ) // getenv: read-only env lookup, no _dupenv_s churn needed
#endif
const char* p = std::getenv ( "P2PMSG_PUMPS_MAX" );
#if defined(_MSC_VER)
# pragma warning( pop )
#endif
unsigned long v = ( p && *p ) ? std::strtoul ( p, nullptr, 10 ) : 15ul;
const unsigned long hi = (unsigned long) MAX_P2PmsgPump; // library hard cap (256)
return (UINT) ( v < 15ul ? 15ul : ( v > hi ? hi : v ) );
} ();
return s_nPumpsMax;
}
// TSan (Risk #3): P2PeerHub::m_nHubID doubles as a cross-thread liveness/completion flag - the
// pump thread stores 0 at exit (RunHub/ProcHub) while caller threads spin-read it (SpawnHub,
// CloseHub). It can't be a plain std::atomic member because CreateThread() writes it through a
// raw DWORD* (the thread-id out-param), so wrap the CROSS-THREAD accesses in std::atomic_ref:
// the caller-side reads and the pump-side exit store. The pump's own reads stay sequenced with
// its store (same thread) and CreateThread's spawn-time write happens-before the pump runs, so
// those need no wrapping. acquire/release pairs the store-0 with the spinning loads.
static inline P2PmsgHubID LoadHubID ( const P2PmsgHubID& r )
{ return std::atomic_ref<P2PmsgHubID>(const_cast<P2PmsgHubID&>(r)).load(std::memory_order_acquire); }
static inline void StoreHubID ( P2PmsgHubID& r, P2PmsgHubID v )
{ std::atomic_ref<P2PmsgHubID>(r).store(v, std::memory_order_release); }
///////////////////////////////////////////////////////////////////////
// Constructors and destructor
//
// Constructors and destructor
//
// Parameters: P2PaddrSTR strP2PaddrHub
// Unique network global identification
// NOTES: P2Paddress's are only ever assigned to objects
// derived from this class.
// : May be NULL in which case P2Paddress is negotiated
// and allocated as part of a connection login
// sequence.
//
P2PeerHub::P2PeerHub ( P2PaddrSTR strP2PaddrHub )
: P2PeerTarget ( (P2PeerTarget *)nullptr, 0 )
{
// Firstly
RenderHubSafe ( );
SetP2PaddrHub ( strP2PaddrHub );
}
//
// Reports a hub destroyed with its spawned pump thread still running
// NOTES: SEPARATED OUT so the destructor reads as one statement of the
// contract rather than four of the mechanism, and so the long note
// lives with the check rather than in the middle of the teardown
// : TRUE only for the case that is actually unsafe. m_hHubExit is armed
// by SpawnHub and only for the default ProcHub trampoline, so a hub
// run by CreateHub() in the caller's own context - which has no second
// thread to race - answers FALSE and says nothing. So does a hub
// being destroyed ON its own pump thread, where there is no other
// thread to be inside
// : A zero-timeout wait, never a blocking one. The question is "has
// ProcHub finished its epilogue", which the manual-reset event already
// answers without waiting; blocking here would hide the defect by
// fixing it too late instead of reporting it
//
bool
P2PeerHub::HubSpawnedAndRunning ( )
{
HANDLE hExit = 0;
{
P2PsafeCS oSafeCS = m_oCSectionHub;
if ( m_hHubExit && m_nHubThreadID != GetCurrentThreadId ( ) )
hExit = m_hHubExit;
}
return hExit && WaitForSingleObject ( hExit, 0 ) != WAIT_OBJECT_0;
}
P2PeerHub::~P2PeerHub ( )
{
// THE CONTRACT, CHECKED RATHER THAN ASSUMED (F-TSAN-2, 2026-09-17).
//
// CloseHub() below shuts a spawned hub down completely and correctly. It
// is also, by construction, TOO LATE - and that is not a bug in CloseHub,
// it is where a base destructor sits in the sequence:
//
// ~WaiveHub derived members destroyed, derived object gone
// ~P2PeerHub THE VTABLE POINTER IS REWRITTEN HERE, on entry
// ~P2PeerHub body ...and only now does CloseHub() stop the thread
//
// Everything above the third line happens while the pump thread is still
// running, still dispatching virtuals through this object and still
// reaching into the derived half that no longer exists. TSan caught it as
// a vptr race in p2p_e2ewaive and the second report is the one that
// settles it: the pump thread was inside P2PeerCon::GetAuthHub() from
// SealAppMsgOutbound - MID-SEAL - as the destructor rewrote the pointer
// it was about to dispatch through.
//
// So CloseHub() must COMPLETE BEFORE DESTRUCTION BEGINS, which only the
// owner can arrange: `CloseHub();` as the first statement of the
// most-derived destructor, or an explicit call before the object goes out
// of scope. No base class can do it for them, and this one had been
// quietly pretending otherwise since the destructor first called
// CloseHub().
//
// WHY THIS REPORTS INSTEAD OF ASSERTING, given SpawnHub's own contract
// violation two screens down is a plain ASSERT. That one fires on a
// programming error the caller can fix in place. This one fires on a
// race that is already half-run by the time we are here - aborting turns
// a reported defect into a Debug-only crash in every consumer that has
// the bug today, and this library has 87 hub subclasses across seven
// repositories. Report loudly, close as best we can, and let the gate
// measure the rest.
//
// AND IT IS SAFE TO REPORT HERE, which a destructor running at process
// teardown would not normally be. The condition is "a pump thread is
// still running" - so the P2Pmsg environment cannot already have been
// through CleanupP2Pmsg(), because a live pump is exactly what that
// refuses to leave behind.
if ( HubSpawnedAndRunning ( ) )
EVERR->Module (L"P2PeerHub::~P2PeerHub" )
->Message(L"Hub destroyed while its spawned pump thread was still "
"running. Call CloseHub() BEFORE destruction begins - "
"from the most-derived destructor, or at the owner. By "
"the time this base destructor runs the derived object "
"is gone and the vtable pointer has been rewritten, and "
"the pump thread is still dispatching through both." )
->Cancel();
CloseHub ( );
// After CloseHub(): the pumps are down - and since CloseHub() waits on the
// hub-exit event, "down" now means the thread has actually left ProcHub
// rather than merely having flagged its hub id clear. So nothing can be
// inside a handshake reaching for the policy while it is being freed, and
// nothing is left to enter m_oCSectionHub after it is deleted.
// NOTES: That wait only orders anything at all because the event now
// publishes a happens-before edge. Until Msgcore 6cc6939 a
// manual-reset wait merely poll()ed the eventfd on Linux and
// published nothing, so this sequence was ordered by the Sleep(1)
// in YieldForP2PmsgPump and by nothing else.
delete m_pAuthPolicy;
m_pAuthPolicy = 0;
// The exit event's owner is the hub, so this is where it is released -
// on the ordinary path as well as the odd one. CloseHub() above only
// WAITS on it; it deliberately neither takes it out of the member nor
// closes it, because SignalHubExit reads the same member and a CloseHub
// that nulled it first would be waiting for a signal it had made
// unreachable. Refer CloseHub.
if ( m_hHubExit )
{
CloseHandle ( m_hHubExit );
m_hHubExit = 0;
m_nHubThreadID = 0;
}
DeleteCriticalSection ( &m_oCSectionHub );
}
void
P2PeerHub::RenderHubSafe()
{
// Attributes
m_nHubID = 0; // Inactive hub flag
m_pP2PeerExpump = 0; // P2PeerExp object
m_hHubExit = 0; // No spawned pump thread to wait for
m_nHubThreadID = 0;
// Resources
InitializeCriticalSection ( &m_oCSectionHub );
// Login policy
// NOTES: Allocated unconditionally so no call site has to null-check it.
// An unconfigured policy signs nothing and requires nothing, which
// is precisely the pre-existing behaviour.
m_pAuthPolicy = new p2pauth::AuthPolicy ( );
}
///////////////////////////////////////////////////////////////////////
// Hub management
// NOTES: Started using default P2PeerHub::SpawnHub() or
// CreateHub() implementations
typedef struct
{
P2PmsgHubID *pnHubID;
P2PeerHub *pHub;
P2Paddr oP2Paddr;
UINT nPumpsMax;
RUN_HUB pfnRunHub;
} P2ProcContext;
//
// Spawns P2PmsgHub and commences pumping in a new thread
// NOTES: Thread processing may be stopped via CloseHub()
// : P2PeerTarget contains equivalent implementation for pumps
//
//
// Parameters: LPTHREAD_START_ROUTINE pfnThreadProc = 0
// The thread procedure of the new thread. Refer
// win32 CreateThread() for further details
//
// RUN_HUB pfnRunHub = 0
// Operational method of the new thread
//
// Returns: HANDLE
// Returns the handle to the newly created thread
// or NULL on failure.
//
HANDLE
P2PeerHub::SpawnHub ( LPTHREAD_START_ROUTINE pfnThreadProc
, RUN_HUB pfnRunHub )
{
// Preparation
ASSERT(m_nHubID == 0 );
// Stage 3 step 8. Before the thread exists, so a hub that cannot enforce
// what it requires never gets one - the alternative is a pump thread
// that starts, refuses every peer, and looks healthy from outside.
if ( !AuthArmOrRefuse ( _T(__FUNCTION__) ) )
return 0;
if ( pfnThreadProc == NULL )
pfnThreadProc = P2PeerHub::ProcHub;
if ( pfnRunHub == NULL )
pfnRunHub = RUN_HUB_cast(&P2PeerHub::RunHub);
// Pass context through
P2ProcContext oContext;
oContext.pnHubID = &m_nHubID;
oContext.pHub = this;
oContext.pfnRunHub = pfnRunHub;
oContext.oP2Paddr = m_oP2PaddrHub;
oContext.nPumpsMax = HubPumpsMaxKnob ( ); // W8: env-tunable, default 15
// Arm the exit event BEFORE the thread exists
// NOTES: The thread can finish before CreateThread returns here, so an
// event created afterwards could be created after the only SetEvent
// that would ever have signalled it.
// : ONLY WHEN ProcHub IS THE TRAMPOLINE, and that is a scope rather
// than a compromise. ProcHub is what sets the event, so arming it
// for a caller-supplied thread proc would leave CloseHub waiting
// for ever on a signal nothing raises - trading a race for a hang.
// A custom trampoline also does not call CloseP2PmsgHub(), which is
// the call the race is between, so the defect this closes does not
// exist on that path: it keeps exactly today's behaviour, the spin.
// : A previous spawn's event is closed first. Re-spawning a hub that
// was never closed is already a contract violation (the ASSERT at
// the top of this function), but leaking a handle on top of it
// would make the next reader's problem the wrong one.
// : AND THE ARMING GATE ABOVE NEEDS NO SPECIAL CASE, which was the
// open question this fix sat unmerged for. Stage 3 step 8's
// AuthArmOrRefuse() returns BEFORE any of this, so a hub that
// refuses to arm never reaches these lines: no thread is created
// and no event is armed, and CloseHub finds nothing to wait for.
if ( m_hHubExit )
{
CloseHandle ( m_hHubExit );
m_hHubExit = 0;
}
if ( pfnThreadProc == P2PeerHub::ProcHub )
m_hHubExit = CreateEvent ( 0, TRUE /*manual reset*/, FALSE, 0 );
m_nHubThreadID = 0;
// Dedicated P2PmsgHub thread creation
// NOTES: Spawned hub runs under context of this thread and hence
// the allocated win32 threadID becomes the P2PmsgHubID
HANDLE hThread = CreateThread ( 0, 0
, pfnThreadProc, &oContext
, 0, &m_nHubID );
if ( !hThread )
{
// No thread means nothing will EVER set the event, and CloseHub's wait
// is unbounded. Disarming here is what keeps a failed spawn a failed
// spawn instead of a hang at teardown.
if ( m_hHubExit ) { CloseHandle ( m_hHubExit ); m_hHubExit = 0; }
return FALSE;
}
// The pump thread's id, kept so CloseHub can refuse to wait for ITSELF.
// Taken from the DWORD CreateThread just wrote rather than from
// m_nHubID later on, because the thread stores 0 there as its last act.
m_nHubThreadID = (DWORD)m_nHubID;
// Confirm operation
// NOTES: Contracted for P2PmsgHub operation upon return
DWORD dwExitCode;
UINT uSpins = 0;
while ( !P2PmsgHubExists(LoadHubID(m_nHubID)) )
{
if ( !GetExitCodeThread(hThread,&dwExitCode) ||
dwExitCode != STILL_ACTIVE )
return 0; // Operational failure
YieldForP2PmsgPump ( uSpins ); // Yield (escalating - see header)
}
// Tidy up and
return P2PmsgHubExists(LoadHubID(m_nHubID)) ? hThread : 0;
}
//
// Creates P2PmsgHub within the context of this thread
// NOTES: Thread processing may be stopped via CloseHub()
// : Facilitates integration of P2PeerHub processing in
// 3rd Party environments such as MFC or service thread
//
//
// Parameters: P2PaddrSTR strP2PaddrHub
// Hub network address
//
// : UINT nPumpsMax
// Maximum number of P2PmsgPumps supported by the hub
//
// Returns: BOOL
// Success code
// TRUE... Created OK
// FALSE.. Failure
//
BOOL
P2PeerHub::CreateHub ( P2PaddrSTR strP2PaddrHub
, UINT nPumpsMax )
{
// Hub is created an run in context of calling thread
// NOTES: Client is reasponsible for pumping messages through
// the P2PmsgHub
ASSERT(m_nHubID == 0 );
m_oP2PaddrHub = strP2PaddrHub;
// Stage 3 step 8. Set the address first, so the refusal can name the hub
// it is refusing; arm nothing if the hub cannot enforce what it requires.
if ( !AuthArmOrRefuse ( _T(__FUNCTION__) ) )
return FALSE;
m_nHubID = CreateP2PmsgHub ( strP2PaddrHub, this, nPumpsMax, 1 );
// Tidy up and
return P2PmsgHubExists(m_nHubID);
}
//
// Pause all hub operations
// NOTES: Upon completion hub effectively exists in idle space
// : Both hub context and non-hub context OK
void
P2PeerHub::PauseHub ( )
{
// Pause processing must be performed from context of this hub
// NOTES: OK to re-signal from any other non-hub context
if ( GetCurrentThreadId() != GetHubID() )
{
if ( GetHubID() > 0 )
SignalP2PmsgHub ( GetHubID(), P2PsigHub_PAUSE );
return;
}
// Close all idle client connections
ConSignal ( L"*", P2PsigCon_CLOSEONIDLE );
}
//
// Wakeup all hub operations
// NOTES: Upon completion hub effectively operational
// : Both hub context and non-hub context OK
// : Cancels a PauseHub() that has not finished draining. PauseHub
// latches ConState_CloseOnIdle on every connection and drops the ones
// already idle, so wakeup is the inverse of the latch and nothing
// more: connections still carrying traffic when the pause arrived
// survive it, connections the pause already dropped do not come back.
// Reviving those is the connection owner's business - the default
// ON_P2PeerCon_CLOSE handler already restarts a CLIENT after
// m_uAutoRestart, and a SERVICE listener must be re-armed by whoever
// knows what it was listening on.
// : Until Stage 3 this asserted and returned, which made PauseHub a
// one-way door through p2peerhub_wakeup_hub(), the service
// SERVICE_CONTROL_CONTINUE handler and P2PsigHub_WAKEUP alike.
void
P2PeerHub::WakeupHub ( )
{
// Wakeup processing must be performed from context of this hub
// NOTES: OK to re-signal from any other non-hub context
if ( GetCurrentThreadId() != GetHubID() )
{
if ( GetHubID() > 0 )
SignalP2PmsgHub ( GetHubID(), P2PsigHub_WAKEUP );
return;
}
// Un-latch close-on-idle across every surviving connection
ConSignal ( L"*", P2PsigCon_WAKEUP );
}
//
// Close hub and associated pumps managed through this hub
// NOTES: Hub processing may be re-started via P2Peer::SpawnHub()
// or CreateHub()
//
void
P2PeerHub::CloseHub ( )
{
// Asynchronous closure
// NOTES: P2PmsgHub exists in another context
P2PmsgHubID nHubID = LoadHubID(m_nHubID);
if ( nHubID > 0 &&
nHubID != GetCurrentThreadId() )
{
SignalP2PmsgHub ( nHubID, P2PsigHub_CLOSE );
UINT uSpins = 0;
while ( (nHubID = LoadHubID(m_nHubID)) > 0 && P2PmsgPumpExists(nHubID) )
YieldForP2PmsgPump ( uSpins );
}
// P2PeerExp closure
DropP2PeerExpump ( );
// Synchronous closure
// NOTES: P2PmsgHub exists in this context
nHubID = LoadHubID(m_nHubID);
if ( nHubID > 0 &&
nHubID != GetCurrentThreadId() )
CloseP2PmsgHub ( );
// Wait for the spawned pump thread to LEAVE
// NOTES: THE SPIN ABOVE IS NOT THE END OF THAT THREAD, and this is the
// whole reason this wait exists. It ends when m_nHubID reads zero,
// which RunHub() stores as its own last act - so at that moment a
// derived RunHub()'s tail (anything a subclass does after calling
// the base) and then ProcHub()'s CloseP2PmsgHub() are both still to
// run on that thread. Returning to the caller there let
// P2PeerService::Run() reach CleanupP2Pmsg(), which
// DeleteCriticalSection()s s_oCSectionP2PmsgHub and ...Pump, while
// CloseP2PmsgHub()'s first two statements take exactly those: an
// access violation inside ntdll's RtlEnterCriticalSection, on
// roughly one console run in three of the ErrorReportingExamples
// NTServiceEventLog harness, with the main thread already inside
// exit(). The same window covers ~P2PeerService's delete of the
// hub and the host's own teardown.
// : UNBOUNDED, as the spin above is. A pump that cannot leave is the
// condition CloseHub() is documented to wait out rather than paper
// over - refer SECURITY.md - and a timeout here would restore this
// exact race on a loaded machine.
// : Never waits for itself. CloseHub() from a handler runs ON the
// pump thread; the event is left for the out-of-context call that
// follows, or for the destructor.
// : IT ONLY READS THE MEMBER. It does not take the event out and it
// does not close it, and the first version of this did both - which
// HUNG p2p_daemon_harness on the first full suite run after it
// landed. Nulling m_hHubExit before waiting meant SignalHubExit,
// which reads the same member, found nothing to set: CloseHub was
// then waiting for ever on a signal it had just made unreachable.
// Blocked with zero CPU, which is what an unbounded wait looks like
// from outside and is why it was not a spin.
// : So the event's lifetime belongs to the HUB, not to this call.
// ~P2PeerHub closes it, and a re-spawn closes the previous one
// before arming a new one. Two concurrent CloseHub()s therefore
// both wait on a manual-reset event and both return, with nothing
// to double-close; a re-spawn while one is still waiting is already
// a contract violation the ASSERT at the top of SpawnHub catches.
HANDLE hExit = 0;
{
P2PsafeCS oSafeCS = m_oCSectionHub;
if ( m_hHubExit && m_nHubThreadID != GetCurrentThreadId ( ) )
hExit = m_hHubExit;
}
if ( hExit )
WaitForSingleObject ( hExit, INFINITE );
}
//
// Signals that this hub's spawned pump thread has finished
// NOTES: Called by ProcHub as its VERY LAST statement, and the position is
// the contract. CloseHub() may be blocked on this event, and it
// closes the hub the moment it is set - so anything the thread did
// after setting it would be touching an object its owner is entitled
// to have destroyed.
// : Silent when nothing is armed. A hub started with CreateHub(), or
// with a caller-supplied thread proc, has no event and needs none -
// refer SpawnHub
// : Reads the member under the lock because CloseHub may be taking it
// out from under this call at the same moment
//
void
P2PeerHub::SignalHubExit ( )
{
HANDLE hExit = 0;
{
P2PsafeCS oSafeCS = m_oCSectionHub;
hExit = m_hHubExit;
}
if ( hExit )
SetEvent ( hExit );
}
//
// Default thread starting address nominated in CreateThread()
// NOTES: Provide alternative implementation to override default
// action.
//
//
// Parameters: void *pvData
// Thread data passed via CreateThread()
//
// Returns: DWORD
// Completion code
//
DWORD WINAPI
P2PeerHub::ProcHub ( void *pvData )
{
// Introduce locals
// NOTES: P2ProcContext is assumed not to persist
P2ProcContext *pContext = (P2ProcContext *)pvData;
P2Paddr oP2Paddr = pContext -> oP2Paddr;
P2PeerHub *pHub = pContext -> pHub;
P2PmsgHubID *pnHubID = pContext -> pnHubID;
UINT nPumpsMax= pContext -> nPumpsMax;
RUN_HUB pfnRunHub = pContext -> pfnRunHub;
pHub -> P2PeerHub::AssertValid ( );
// Mandatory P2PeerHub thread environment
// NOTES: Sequence contains mandatory P2PeerHub and P2PmsgHub
// life cycle management sequences
// : Guard the whole hub life cycle. An exception escaping a thread
// proc terminates the process (std::terminate on Linux, likewise on
// Windows). RunHub() self-guards its own pump loop, but the
// CreateP2PmsgHub() setup and CloseP2PmsgHub() teardown-drain run
// outside it and can throw a P2Pevent under socket/port pressure -
// which aborted the process on a pump thread (Phase-5 teardown
// stress). Mirrors the already-guarded P2PeerTarget::ProcPump;
// ProcHub/ProcExpump were the only unguarded trampolines.
try
{
CreateP2PmsgHub ( oP2Paddr, pHub, nPumpsMax, 8 );
(pHub->*pfnRunHub) ( ); // Nominated Run() method
if ( pnHubID && *pnHubID == GetCurrentThreadId() )
StoreHubID ( *pnHubID, 0 ); // Flags pump closure (atomic_ref, TSan Risk #3)
CloseP2PmsgHub ( );
}
catch ( P2Pevent *pEVT )
{
pEVT->Advice(_T("P2PeerHub::ProcHub terminated"))->Cancel();
}
catch ( ... )
{
EVERR->Module (L"P2PeerHub::ProcHub" )
->Message(L"Last resort exception of unknown type, "
"P2PmsgHub terminated" )
->Cancel();
}
// Tidy up, and thread is dead
//pHub -> PostDestroyHub ( );
// THE LAST STATEMENT, and it has to be. CloseHub() may be blocked on this
// event and will close - and its caller may destroy - the hub as soon as
// it is set, so anything below this line would be touching an object its
// owner is entitled to have finished with. Refer SignalHubExit.
pHub -> SignalHubExit ( );
return 0;
}
//
// Plain network P2PmsgHub implementation
// NOTES: Simply pumps P2PeerSys, P2PeerCon and P2PeerMsg objects
// through the P2PeerTarget base class
// : P2PeerMsg's can be swapped out of this context via
// ContextSwap() and processed independantly in the fullness
// of time
// : It's possible to have processing delays in this context.
// But keep in mind P2PeerMsg's may keep building up
//
void
P2PeerHub::RunHub ( )
{
// Introduce locals
DWORD dwResult;
P2PsigID nSigID;
DWORD dwMSec = 8000;
ASSERT(m_nHubID==GetCurrentThreadId());
// Because sequence must end orderly
try
{
// Latencies
SetP2PmsgPumpFunc ( 0, _T(__FUNCTION__) );
ThrowP2Pevent();
// Pump messages through the P2PeerSys, P2PeerCon and
// P2PeerMsg_MAP's until terminated and exhausted
while ( (dwResult=PumpP2Pmsg(dwMSec,nSigID)) != 0 )
{
// Signal - Immediate closure
if ( nSigID == P2PsigHub_CLOSE )
{
P2PeerCon *pCon = 0;
while ( EnumP2PmsgCon(m_nHubID,&pCon) )
pCon -> Signal ( P2PsigCon_DESTROY );
P2PumpID nPumpID = 0;
while ( EnumP2PmsgPump(m_nHubID,nPumpID) )
SignalP2PmsgPump ( nPumpID, P2PsigPump_CLOSE );
break;
}
// Signal - Idle closure
if ( nSigID == P2PsigHub_CLOSEONIDLE )
{
P2PeerCon *pCon = 0;
while ( EnumP2PmsgCon(m_nHubID,&pCon) )
pCon -> Signal ( P2PsigCon_CLOSEONIDLE );
P2PumpID nPumpID = 0;
while ( EnumP2PmsgPump(m_nHubID,nPumpID) )
SignalP2PmsgPump ( nPumpID, P2PsigPump_CLOSEONIDLE );
break;
}
// Signal - Pause
if ( nSigID == P2PsigHub_PAUSE )
PauseHub ( );
// Signal - Wakeup
if ( nSigID == P2PsigHub_WAKEUP )
WakeupHub ( );
ASSERT(m_nHubID==GetCurrentThreadId());
}
}
// Exceptions
catch ( P2Pevent *pEVT )
{
pEVT->Advice(_T("P2PeerHub(%s) P2PmsgHub terminated")
, m_oP2PaddrHub.c_wstr() )
->Cancel();
}
catch ( ... )
{
EVERR->Module (_T(__FUNCTION__) )
->Message(_T("Last resort exception of unknown type") )
->Advice (_T("P2PeerHub(%s) P2PmsgHub terminated")
, m_oP2PaddrHub.c_wstr() )
->Cancel();
}
// Tidy up, and
// NOTES: Manadatory last operation. atomic_ref store pairs with the caller-thread
// LoadHubID spin-reads in SpawnHub/CloseHub (TSan Risk #3).
StoreHubID ( m_nHubID, 0 );
}
//
// Performs post P2PmsgHub destruction processing
// NOTES: Specialise for external notifications etc
//
void
P2PeerHub::PostDestroyHub ( )
{
ASSERT(m_nHubID==0);
}
///////////////////////////////////////////////////////////////////////
// P2PeerCon management
//
// Posts P2PeerCon object to this hub
// NOTES: Control over life cycle of posted P2PeerCon object is
// assumed. Either service or client type objects may be
// posted
//
//
// Parameters: P2PeerCon *pCon
// Connection object to be posted
//
// P2PumpID nPumpID = 0
// Identification code of pump to which P2PeerCon
// object is to be posted
// 0.. Defaults to P2PmsgHub context
//
// Returns: BOOL
// Completion summary flag
// TRUE.. P2PeerCon object posted
// FALSE.. P2Pevent generated
BOOL
P2PeerHub::PostP2PeerCon ( P2PeerCon *pCon, P2PumpID nPumpID )
{
// Introduce locals
SafeP2PeerCon sppCon = pCon;
P2Paddr oP2PaddrCon = pCon -> GetP2Paddress();
P2PsafeCS oSafeCS = m_oCSectionHub;
if ( g_bP2Pmsg_AssertValid )
pCon -> AssertValid ( );
if ( nPumpID <= 0 )
nPumpID = m_nHubID;
// Environmental
// NOTES: P2PeerCon objects may only be posted to runnning hubs,
// negates risk of dead connections.
if ( m_nHubID == NULL )
{
EVERR->Module (_T(__FUNCTION__) )->AFPcon(pCon)->AFP(nPumpID)
->Message(_T("P2PeerHub(%s) is not operational, ")
_T("P2PeerCon(%s) not posted")
, oP2PaddrCon.c_wstr()
, m_oP2PaddrHub.c_wstr() )
->Advice (_T("Perform SpawnHub() or CreateHub() before PostP2PeerCon()") )
->Advice (_T("Hub has failed?") )
->Display()->SetLast();
return FALSE;
}
// THE FENCE
// NOTES: RequireTrustAtLeast() names the lowest class this hub will hold a
// link of, and this is where that is enforced. It is the other
// half of SetLinkPolicy: a hub that opened its in-process class is
// otherwise one call to this function away from carrying a socket
// it did not plan for. That socket would authenticate IN FULL -
// the wire's policy is Full and cannot be set otherwise - so the
// refusal is not about weakening; it is about a hub that exists to
// route inside a process quietly becoming a network endpoint
// : EffectiveTrust() and not TrustClass(), so a connection the
// operator demoted is measured on the class they demoted it TO.
// Every other gate in this feature reads the same value
// : BEFORE the duplicate scan, and before m_pP2PeerTarget is
// assigned, so a refused connection leaves this hub in the state it
// was in. The caller keeps ownership either way, exactly as it
// does for the two refusals around this one
// : An ACCEPTED child does not come through here - P2PeerCon::
// AcceptSpawn posts it directly - and it does not need to. It is
// spawned by a SERVICE that was fenced when the service was posted,
// it inherits the class through a virtual and the ceiling through
// the one copied field, and neither of those can read HIGHER than
// the service's own. A service admitted by the fence cannot accept
// a child the fence would have refused
// : The default floor is P2PeerConTrust_Wire, which is 0, which is
// what every transport that has vouched for nothing answers. So an
// unconfigured hub compares 0 < 0 and refuses nothing
// eFloor is tested FIRST so that a hub nobody has fenced does not ask
// the connection anything at all. EffectiveTrust() on a socket is a
// getpeername(), which is cheap and is still a syscall this function did
// not make before; an unconfigured hub must be able to compare 0 < 0 and
// reach the same instruction it reached yesterday.
const P2PeerConTrust_e eFloor = GetRequiredTrust ( );
if ( eFloor > P2PeerConTrust_Wire &&
pCon -> EffectiveTrust ( ) < eFloor )
{
EVERR->Module (_T(__FUNCTION__) )->AFPcon(pCon)->AFP(nPumpID)
->Message(_T("P2PeerHub(%s) holds no link below trust class %i; ")
_T("P2PeerCon(%s) is class %i and is not posted")
, m_oP2PaddrHub.c_wstr()
, (int)eFloor
, oP2PaddrCon.c_wstr()
, (int)pCon -> EffectiveTrust ( ) )
->Advice_T ("0 wire, 1 kernel-local, 2 in-process. Post a connection "
"of the class this hub was fenced to, or widen the fence "
"with RequireTrustAtLeast()")
->Display()->SetLast();
return FALSE;
}
// Iterate through P2PeerCon list
// NOTES: Trap duplicates. Null identification addresses
// are special
P2PeerCon *pConEnum = 0;
while ( EnumP2PmsgCon(m_nHubID,&pConEnum) )
{
if ( pConEnum->GetP2Paddress() != oP2PaddrCon )
continue;
EVERR->Module (_T(__FUNCTION__))->AFPcon(pCon)->AFP(nPumpID)
->Message(_T("P2PeerCon[%s] instance already exists ")
_T("within P2PmsgHub[%s]")
, oP2PaddrCon.c_wstr()
, m_oP2PaddrHub.c_wstr() )
->SetLast();
return FALSE;
}
// Tidy up, and
pCon -> m_pP2PeerTarget = this;
PostP2PmsgCon ( nPumpID, pCon );
// NB: post the con's LIVE address (m_oThatP2Paddr, valid for pCon's lifetime),
// NOT the stack-local copy oP2PaddrCon — P2Pmsg.strP2Paddr keeps a raw pointer and
// the pump dispatches CN_P2PeerCon on another thread after this frame returns, so a
// pointer into oP2PaddrCon is a use-after-return (ASan stack-use-after-return; masked
// on Windows only because the pointed-at CString buffer is heap/refcounted). Every
// other CN_P2PeerCon post already passes GetP2Paddress() for exactly this reason.
PostP2Pmsg ( pCon->GetP2Paddress(), CN_P2PeerCon, P2P_Startup
, pCon, (P2PeerMsg *)0, nPumpID );
return TRUE;
}
//
// Signals nominated P2PeerCon object
//
//
// Parameters: P2PaddrSTR strP2Paddr
// Address domain defining of P2PeerCon objects to
// be signalled
//
// P2PconID nConID
// Identification of the P2PeerCon object to be signalled
//
// P2PsigID nSigID
// Signal to be posted
//
// void *pvData = 0
// Signal data
//
// int iDataSize = 0
// Signal data size
//
// Returns BOOL
// Signalled event summary
// TRUE... Located
// FALSE.. Do not exist
BOOL
P2PeerHub::ConSignal ( P2PaddrSTR strP2PaddrTP
, P2PsigID nSigID, void *pvData, int iDataSize )
{
// Introduce locals
P2Paddr oP2Paddr = strP2PaddrTP;
BOOL bResult = FALSE;
P2PsafeCS oSafeCS = m_oCSectionHub;
ASSERT(m_nHubID>0);
// Iterate through P2PeerCon'nections list
P2PeerCon *pConEnum = 0;
while ( EnumP2PmsgCon(m_nHubID,&pConEnum) )
{
if ( !oP2Paddr.IsMapped(pConEnum->GetP2Paddress()) )
continue;
bResult = TRUE;
pConEnum -> Signal ( nSigID, pvData, iDataSize );
SwitchToThread ( );
}
// Tidy up and
return bResult;
}
BOOL
P2PeerHub::ConSignal ( P2PconID nConID
, P2PsigID nSigID, void *pvData, int iDataSize )
{
// Introduce locals
BOOL bResult = FALSE;
P2PsafeCS oSafeCS = m_oCSectionHub;
ASSERT(m_nHubID>0);
// Iterate through P2PeerCon'nections list
P2PeerCon *pConEnum = 0;
while ( EnumP2PmsgCon(m_nHubID,&pConEnum) )
{
if ( nConID &&
nConID != pConEnum->m_nP2PconID )
continue;
bResult = TRUE;
pConEnum -> Signal ( nSigID, pvData, iDataSize );
SwitchToThread ( );
}
// Tidy up and
return bResult;
}
//
// Checks if P2PeerCon connection object exists for passed
// P2PaddrSTR
//
//
// Parameters: P2PaddrSTR strP2Paddr
// Connection identification code to be checked
//
// Returns BOOL
// Existence summary
// TRUE... Exists
// FALSE.. Does not exist
BOOL
P2PeerHub::ConExists ( P2PaddrSTR strP2Paddr )
{
// Locals
P2PsafeCS oSafeCS = m_oCSectionHub;
// Iterate through P2PeerCon list
P2PeerCon *pCon = 0;
while ( EnumP2PmsgCon(m_nHubID,&pCon) )
{
if ( pCon->GetP2Paddress() == strP2Paddr )
return TRUE;
}
// Nope, does not exist
return FALSE;
}
//
// Queries hub for nominated P2PeerCon
// NOTES: Designed for use from P2PeerMsg handlers whereby
// configuration issues require access to underlying
// P2PeerCon'nections
// : P2PeerCon pointers are transitory and as such should
// only retained for the life of a P2PeerMsg handler
//
//
// Parameters: P2PaddrSTR strP2Paddr
// Connection idetification
//
// SafeP2PeerCon& rSafeCon
// Safe container
//
// Returns: bool
// Query summary
// true... Located
// false.. Not located
bool
P2PeerHub::ConQuery ( P2PaddrSTR strP2Paddress, SafeP2PeerCon& rSafeCon )
{
// Locals
P2PsafeCS oSafeCS = m_oCSectionHub;
// Iterate through P2PeerCon list
P2PeerCon *pCon = 0;
while ( EnumP2PmsgCon(m_nHubID,&pCon) )
{
if ( pCon->GetP2Paddress() == strP2Paddress )
{
rSafeCon = pCon;
return true;
}
}
// Nope, does not exist
rSafeCon = (P2PeerCon *)0;
return false;
}
///////////////////////////////////////////////////////////////////////
// P2PeerMsg operations
//
// Routes P2PeerMsg's through P2PeerCon objects posted to this hub
// NOTES: Local P2PeerMsg routing previously handled
// : Referenced from P2Pwin32, PostP2PeerMsg() for local hub
// routing
//
//
// Parameters: P2PeerMsg *pMsg
// Message to be routed.
//
// Returns: msgRESULT
// Routing result
msgRESULT
P2PeerHub::RouteP2PeerMsg ( P2PeerMsg *pMsg )
{
if ( m_pTargetParent )