Repository navigation
Expand file tree
/
Copy pathSNSLiveEventDataListenerRefactoring.html
More file actions
2004 lines (1823 loc) · 218 KB
/
Copy pathSNSLiveEventDataListenerRefactoring.html
File metadata and controls
2004 lines (1823 loc) · 218 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
<!DOCTYPE html>
<html lang="en" data-content_root="./" >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SNSLiveEventDataListener refactoring — MantidProject main documentation</title>
<script data-cfasync="false">
document.documentElement.dataset.mode = localStorage.getItem("mode") || "";
document.documentElement.dataset.theme = localStorage.getItem("theme") || "";
</script>
<!--
this give us a css class that will be invisible only if js is disabled
-->
<noscript>
<style>
.pst-js-only { display: none !important; }
</style>
</noscript>
<!-- Loaded before other Sphinx assets -->
<link href="_static/styles/theme.css?digest=18bd87630a240e9c25be" rel="stylesheet" />
<link href="_static/styles/pydata-sphinx-theme.css?digest=18bd87630a240e9c25be" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=03e43079" />
<link rel="stylesheet" type="text/css" href="_static/css/style.css?v=562d7d41" />
<!-- So that users can add custom icons -->
<script defer src="_static/scripts/fontawesome.js?digest=18bd87630a240e9c25be"></script>
<!-- Pre-loaded scripts that we'll load fully later -->
<link rel="preload" as="script" href="_static/scripts/bootstrap.js?digest=18bd87630a240e9c25be" />
<link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=18bd87630a240e9c25be" />
<script src="_static/documentation_options.js?v=a8da1a53"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script>DOCUMENTATION_OPTIONS.pagename = 'SNSLiveEventDataListenerRefactoring';</script>
<script>DOCUMENTATION_OPTIONS.search_as_you_type = false;</script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Task Based Algorithms" href="TaskBasedAlgorithms.html" />
<link rel="prev" title="Script Repository" href="ScriptRepository.html" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="docsearch:language" content="en">
<link rel="icon" sizes="32x32" href="_static/images/favicon.ico">
</head>
<body data-default-mode="">
<div id="pst-skip-link" class="skip-link d-print-none"><a href="#main-content">Skip to main content</a></div>
<div id="pst-scroll-pixel-helper"></div>
<button type="button" class="btn rounded-pill" id="pst-back-to-top">
<i class="fa-solid fa-arrow-up"></i>Back to top</button>
<dialog id="pst-search-dialog">
<form class="bd-search d-flex align-items-center"
action="search.html"
method="get">
<i class="fa-solid fa-magnifying-glass"></i>
<input type="search"
class="form-control"
name="q"
placeholder="Search the docs ..."
aria-label="Search the docs ..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"/>
<span class="search-button__kbd-shortcut"><kbd class="kbd-shortcut__modifier">Ctrl</kbd>+<kbd>K</kbd></span>
</form>
</dialog>
<div class="pst-async-banner-revealer d-none">
<aside id="bd-header-version-warning" class="d-none d-print-none" aria-label="Version warning"></aside>
</div>
<header id="pst-header" class="bd-header navbar navbar-expand-lg bd-navbar d-print-none">
<div class="bd-header__inner bd-page-width">
<button type="button" class="pst-navbar-icon sidebar-toggle primary-toggle" aria-label="Site navigation">
<span class="fa-solid fa-bars"></span>
</button>
<div class="col-lg-3 navbar-header-items__start">
<div class="navbar-item">
<a class="navbar-brand logo" href="index.html">
<img src="_static/images/mantid_logo_light.png" class="logo__image only-light" alt="Logo image">
<img src="_static/images/mantid_logo_dark.png" class="logo__image only-dark" alt="Logo image">
</a></div>
</div>
<div class="col-lg-9 navbar-header-items">
<div class="me-auto navbar-header-items__center">
<div class="navbar-item"><ul id="navbar-main-elements" class="navbar-nav">
<li class="nav-item">
<a class="reference internal nav-link" href="https://download.mantidproject.org">Downloads</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://docs.mantidproject.org/nightly/tutorials/">Tutorials</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://docs.mantidproject.org">User Docs</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://developer.mantidproject.org">Develop</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://docs.mantidproject.org/release/">Release notes</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://www.mantidproject.org/contact">Contact Us</a>
</li>
</ul></div>
</div>
<div class="navbar-header-items__end">
<div class="navbar-item navbar-persistent--container">
<button type="button" class="btn search-button-field search-button__button pst-js-only" title="Search" aria-label="Search" data-bs-placement="bottom" data-bs-toggle="tooltip">
<i class="fa-solid fa-magnifying-glass"></i>
<span class="search-button__default-text">Search</span>
<span class="search-button__kbd-shortcut"><kbd class="kbd-shortcut__modifier">Ctrl</kbd>+<kbd class="kbd-shortcut__modifier">K</kbd></span>
</button>
</div>
<div class="navbar-item">
<div class="theme-switch-container dropdown pst-js-only" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Color mode">
<button type="button" class="btn btn-sm nav-link pst-navbar-icon theme-switch-button dropdown-toggle" aria-label="Color mode" data-bs-toggle="dropdown">
<i class="theme-switch fa-solid fa-sun fa-lg fa-fw" data-mode="light" title="Light"></i>
<i class="theme-switch fa-solid fa-moon fa-lg fa-fw" data-mode="dark" title="Dark"></i>
<i class="theme-switch fa-solid fa-circle-half-stroke fa-lg fa-fw" data-mode="auto" title="System Settings"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><button type="button" class="dropdown-item d-flex align-items-center theme-change-button" data-mode="auto"><i class="fa-solid fa-circle-half-stroke fa-lg fa-fw me-1"></i>System Settings</button></li>
<li><button type="button" class="dropdown-item d-flex align-items-center theme-change-button" data-mode="light"><i class="fa-solid fa-sun fa-lg fa-fw me-1"></i>Light</button></li>
<li><button type="button" class="dropdown-item d-flex align-items-center theme-change-button" data-mode="dark"><i class="fa-solid fa-moon fa-lg fa-fw me-1"></i>Dark</button></li>
</ul>
</div></div>
<div class="navbar-item"><ul id="navbar-icon-links" class="navbar-nav" aria-label="Icon Links">
<li class="nav-item">
<a class="nav-link" href="https://github.com/mantidproject/mantid" rel="noopener" target="_blank" title="GitHub">
<span><i class="fab fa-github-square"></i></span>
<label class="sr-only">GitHub</label>
</a>
</li>
</ul></div>
</div>
</div>
<div class="navbar-persistent--mobile">
<button type="button" class="btn search-button-field search-button__button pst-js-only" title="Search" aria-label="Search" data-bs-placement="bottom" data-bs-toggle="tooltip">
<i class="fa-solid fa-magnifying-glass"></i>
<span class="search-button__default-text">Search</span>
<span class="search-button__kbd-shortcut"><kbd class="kbd-shortcut__modifier">Ctrl</kbd>+<kbd class="kbd-shortcut__modifier">K</kbd></span>
</button>
</div>
<button type="button" class="pst-navbar-icon sidebar-toggle secondary-toggle" aria-label="On this page">
<span class="fa-solid fa-outdent"></span>
</button>
</div>
</header>
<div class="bd-container">
<div class="bd-container__inner bd-page-width">
<dialog id="pst-primary-sidebar-modal"></dialog>
<div id="pst-primary-sidebar" class="bd-sidebar-primary bd-sidebar">
<div class="sidebar-header-items sidebar-primary__section">
<div class="sidebar-header-items__center">
<div class="navbar-item"><ul id="navbar-main-elements" class="navbar-nav">
<li class="nav-item">
<a class="reference internal nav-link" href="https://download.mantidproject.org">Downloads</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://docs.mantidproject.org/nightly/tutorials/">Tutorials</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://docs.mantidproject.org">User Docs</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://developer.mantidproject.org">Develop</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://docs.mantidproject.org/release/">Release notes</a>
</li>
<li class="nav-item">
<a class="reference internal nav-link" href="https://www.mantidproject.org/contact">Contact Us</a>
</li>
</ul></div>
</div>
<div class="sidebar-header-items__end">
<div class="navbar-item">
<div class="theme-switch-container dropdown pst-js-only" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Color mode">
<button type="button" class="btn btn-sm nav-link pst-navbar-icon theme-switch-button dropdown-toggle" aria-label="Color mode" data-bs-toggle="dropdown">
<i class="theme-switch fa-solid fa-sun fa-lg fa-fw" data-mode="light" title="Light"></i>
<i class="theme-switch fa-solid fa-moon fa-lg fa-fw" data-mode="dark" title="Dark"></i>
<i class="theme-switch fa-solid fa-circle-half-stroke fa-lg fa-fw" data-mode="auto" title="System Settings"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><button type="button" class="dropdown-item d-flex align-items-center theme-change-button" data-mode="auto"><i class="fa-solid fa-circle-half-stroke fa-lg fa-fw me-1"></i>System Settings</button></li>
<li><button type="button" class="dropdown-item d-flex align-items-center theme-change-button" data-mode="light"><i class="fa-solid fa-sun fa-lg fa-fw me-1"></i>Light</button></li>
<li><button type="button" class="dropdown-item d-flex align-items-center theme-change-button" data-mode="dark"><i class="fa-solid fa-moon fa-lg fa-fw me-1"></i>Dark</button></li>
</ul>
</div></div>
<div class="navbar-item"><ul id="navbar-icon-links" class="navbar-nav" aria-label="Icon Links">
<li class="nav-item">
<a class="nav-link" href="https://github.com/mantidproject/mantid" rel="noopener" target="_blank" title="GitHub">
<span><i class="fab fa-github-square"></i></span>
<label class="sr-only">GitHub</label>
</a>
</li>
</ul></div>
</div>
</div>
<div class="sidebar-primary-items__start sidebar-primary__section">
<div class="sidebar-primary-item pst-sidebar-collapse"><button type="button" id="pst-collapse-sidebar-button" aria-expanded="true" aria-controls="pst-primary-sidebar">
<svg class="pst-icon" role="img" aria-hidden="true" focusable="false" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M3 15.5C2.36232 15.5 1.74874 15.2564 1.28478 14.8189C0.820828 14.3815 0.541576 13.7832 0.504167 13.1467L0.5 13L0.5 3C0.499965 2.36232 0.743605 1.74874 1.18107 1.28478C1.61854 0.820828 2.21676 0.541576 2.85333 0.504167L3 0.5L13 0.5C13.6377 0.499965 14.2513 0.743605 14.7152 1.18107C15.1792 1.61854 15.4584 2.21676 15.4958 2.85333L15.5 3L15.5 13C15.5 13.6377 15.2564 14.2513 14.8189 14.7152C14.3815 15.1792 13.7832 15.4584 13.1467 15.4958L13 15.5L3 15.5ZM3 13.8333L10.5 13.8333L10.5 2.16667L3 2.16667C2.79589 2.16669 2.59889 2.24163 2.44636 2.37726C2.29383 2.5129 2.19638 2.69979 2.1725 2.9025L2.16667 3L2.16667 13C2.16669 13.2041 2.24163 13.4011 2.37726 13.5536C2.5129 13.7062 2.69979 13.8036 2.9025 13.8275L3 13.8333ZM6.65583 10.325L6.5775 10.2558L4.91083 8.58917C4.76735 8.44567 4.68116 8.25476 4.66843 8.05223C4.65569 7.84971 4.71729 7.6495 4.84167 7.48917L4.91083 7.41083L6.5775 5.74417C6.72747 5.59471 6.9287 5.50794 7.14032 5.50148C7.35194 5.49502 7.55809 5.56935 7.7169 5.70937C7.8757 5.8494 7.97525 6.04463 7.99533 6.25539C8.01541 6.46616 7.95451 6.67667 7.825 6.84417L7.75583 6.9225L6.67917 8L7.75583 9.0775C7.89931 9.22099 7.98551 9.41191 7.99824 9.61443C8.01097 9.81695 7.94938 10.0172 7.825 10.1775L7.75583 10.2558C7.61234 10.3993 7.42142 10.4855 7.2189 10.4982C7.01638 10.511 6.81617 10.4494 6.65583 10.325Z"/>
</svg>
<span class="pst-collapse-sidebar-label">Collapse Sidebar</span>
<span class="pst-expand-sidebar-label">Expand Sidebar</span>
</button></div>
<div class="sidebar-primary-item">
<nav class="bd-docs-nav bd-links"
aria-label="Section Navigation">
<p class="bd-links__title" role="heading" aria-level="1">Section Navigation</p>
<div class="bd-toc-item navbar-nav"></div>
</nav></div>
</div>
<div class="sidebar-primary-items__end sidebar-primary__section">
<div class="sidebar-primary-item">
<div id="ethical-ad-placement"
class="flat"
data-ea-publisher="readthedocs"
data-ea-type="readthedocs-sidebar"
data-ea-manual="true">
</div></div>
</div>
</div>
<main id="main-content" class="bd-main" role="main">
<div class="bd-content">
<div class="bd-article-container">
<div class="bd-header-article d-print-none">
<div class="header-article-items header-article__inner">
<div class="header-article-items__start">
<div class="header-article-item">
<nav aria-label="Breadcrumb" class="d-print-none">
<ul class="bd-breadcrumbs">
<li class="breadcrumb-item breadcrumb-home">
<a href="index.html" class="nav-link" aria-label="Home">
<i class="fa-solid fa-home"></i>
</a>
</li>
<li class="breadcrumb-item active" aria-current="page"><span class="ellipsis"><code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener</span></code> refactoring</span></li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<div id="searchbox"></div>
<article class="bd-article">
<section id="snsliveeventdatalistener-refactoring">
<span id="snsliveeventdatalistenerrefactoring"></span><h1><code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener</span></code> refactoring<a class="headerlink" href="#snsliveeventdatalistener-refactoring" title="Link to this heading">#</a></h1>
<p>This document covers the listener-specific aspects of the June 2026 live-listener
refactor (the ADARA packet flow, the deferred <code class="docutils literal notranslate"><span class="pre">RunStatusPkt</span></code> handling,
the <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code> back-pressure mechanism, and the state-machine
diagrams) for <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener</span></code>.</p>
<p>For the generic interface — the four pure-getter queries, the
<code class="docutils literal notranslate"><span class="pre">extractData()</span></code> template method, the migration recipe, and worked
examples for non-SNS listeners — see <a class="reference internal" href="LiveListenerMigration.html#livelistenermigration"><span class="std std-ref">Live-Listener API Migration Guide (v3 refactoring)</span></a>.</p>
<nav class="contents local" id="contents">
<ul class="simple">
<li><p><a class="reference internal" href="#scope" id="id1">Scope</a></p></li>
<li><p><a class="reference internal" href="#adara-protocol-context" id="id2">ADARA protocol context</a></p>
<ul>
<li><p><a class="reference internal" href="#two-orthogonal-state-axes" id="id3">Two orthogonal state axes</a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#pre-refactor-problem" id="id4">Pre-refactor problem</a></p></li>
<li><p><a class="reference internal" href="#new-state-model" id="id5">New state model</a></p>
<ul>
<li><p><a class="reference internal" href="#single-slot-invariant" id="id6">Single-slot invariant</a></p></li>
<li><p><a class="reference internal" href="#header-summary" id="id7">Header summary</a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#state-machine-diagrams" id="id8">State-machine diagrams</a></p>
<ul>
<li><p><a class="reference internal" href="#pre-refactor-transitions-committed-inside-runstatus" id="id9">Pre-refactor: transitions committed inside <code class="docutils literal notranslate"><span class="pre">runStatus()</span></code></a></p></li>
<li><p><a class="reference internal" href="#post-refactor-transitions-committed-inside-extractdata" id="id10">Post-refactor: transitions committed inside <code class="docutils literal notranslate"><span class="pre">extractData()</span></code></a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#implementation" id="id11">Implementation</a></p>
<ul>
<li><p><a class="reference internal" href="#pure-getters" id="id12">Pure getters</a></p></li>
<li><p><a class="reference internal" href="#background-reader-and-packet-handlers" id="id13">Background reader and packet handlers</a></p></li>
<li><p><a class="reference internal" href="#foreground-snapshot-gate-m-bgthreadcaughtup" id="id14">Foreground snapshot gate (<code class="docutils literal notranslate"><span class="pre">m_bgThreadCaughtUp</span></code>)</a></p></li>
<li><p><a class="reference internal" href="#extractdata-the-only-commit-point" id="id15"><code class="docutils literal notranslate"><span class="pre">extractData()</span></code> — the only commit point</a></p></li>
<li><p><a class="reference internal" href="#critical-detail-c1" id="id16">Critical detail (C1)</a></p></li>
<li><p><a class="reference internal" href="#transition-hooks" id="id17">Transition hooks</a></p></li>
<li><p><a class="reference internal" href="#background-reader-loop" id="id18">Background reader loop</a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#poll-driven-reader-loop-and-disconnect-detection" id="id19">Poll-driven reader loop and disconnect detection</a></p>
<ul>
<li><p><a class="reference internal" href="#pre-existing-problems" id="id20">Pre-existing problems</a></p></li>
<li><p><a class="reference internal" href="#the-poll-loop" id="id21">The poll loop</a></p></li>
<li><p><a class="reference internal" href="#disconnect-is-not-an-error-and-where-the-error-gets-ascribed" id="id22">Disconnect is not an error — and where the error gets ascribed</a></p></li>
<li><p><a class="reference internal" href="#surfacing-the-disconnect-to-consumers" id="id23">Surfacing the disconnect to consumers</a></p></li>
<li><p><a class="reference internal" href="#back-pressure-defers-disconnect-detection-known-limitation" id="id24">Back-pressure defers disconnect detection (known limitation)</a></p></li>
<li><p><a class="reference internal" href="#shutdown-path" id="id25">Shutdown path</a></p></li>
<li><p><a class="reference internal" href="#non-goals" id="id26">Non-goals</a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#behaviour-preservation-sns-specific" id="id27">Behaviour preservation (SNS-specific)</a></p></li>
<li><p><a class="reference internal" href="#appendix-pre-existing-defect-m-ignorepackets-is-never-set-true" id="id28">Appendix — pre-existing defect: <code class="docutils literal notranslate"><span class="pre">m_ignorePackets</span></code> is never set <code class="docutils literal notranslate"><span class="pre">true</span></code></a></p>
<ul>
<li><p><a class="reference internal" href="#summary" id="id29">Summary</a></p></li>
<li><p><a class="reference internal" href="#evidence" id="id30">Evidence</a></p></li>
<li><p><a class="reference internal" href="#provenance" id="id31">Provenance</a></p></li>
<li><p><a class="reference internal" href="#impact" id="id32">Impact</a></p></li>
<li><p><a class="reference internal" href="#recommended-fix-separate-pr" id="id33">Recommended fix (separate PR)</a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#testing" id="id34">Testing</a></p>
<ul>
<li><p><a class="reference internal" href="#unit-tests-snsliveeventdatalistenernonetworktest-h" id="id35">Unit tests — <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListenerNoNetworkTest.h</span></code></a></p></li>
<li><p><a class="reference internal" href="#integration-tests-snsliveeventdatalistenertest-h" id="id36">Integration tests — <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListenerTest.h</span></code></a></p></li>
<li><p><a class="reference internal" href="#integration-tests-snsliveeventdatalisteneralgorithmintegrationtest-h" id="id37">Integration tests — <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListenerAlgorithmIntegrationTest.h</span></code></a></p></li>
<li><p><a class="reference internal" href="#legacy-tests-snsliveeventdatalistenerlegacytest-h" id="id38">Legacy tests — <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListenerLegacyTest.h</span></code></a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#further-reading" id="id39">Further reading</a></p></li>
</ul>
</nav>
<section id="scope">
<h2><a class="toc-backref" href="#id1" role="doc-backlink">Scope</a><a class="headerlink" href="#scope" title="Link to this heading">#</a></h2>
<p><code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener</span></code> is the most complex concrete
<code class="docutils literal notranslate"><span class="pre">ILiveListener</span></code> subclass in the tree. It is the <em>only</em> listener
that:</p>
<ul class="simple">
<li><p>maintains a real <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> / <code class="docutils literal notranslate"><span class="pre">EndRun</span></code> edge-detection contract
with its consumers;</p></li>
<li><p>defers run-status side effects (cache clears, workspace re-init,
deferred-packet consumption) across thread boundaries;</p></li>
<li><p>uses an explicit back-pressure flag (<code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code>) between
the background reader and the foreground extractor;</p></li>
<li><p>has a documented pause/resume orthogonal axis driven by ADARA
annotation packets.</p></li>
</ul>
<p>Everything in this document applies to <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener</span></code>
specifically. Other listeners are simpler and are covered in
<a class="reference internal" href="LiveListenerMigration.html#livelistenermigration"><span class="std std-ref">Live-Listener API Migration Guide (v3 refactoring)</span></a>.</p>
</section>
<section id="adara-protocol-context">
<h2><a class="toc-backref" href="#id2" role="doc-backlink">ADARA protocol context</a><a class="headerlink" href="#adara-protocol-context" title="Link to this heading">#</a></h2>
<p>The SNS live-event stream is the ADARA protocol. Five packet types
are relevant to the state machine:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">ADARA::RunStatusPkt</span></code> — carries <code class="docutils literal notranslate"><span class="pre">NEW_RUN</span></code> (run is starting) and
<code class="docutils literal notranslate"><span class="pre">END_RUN</span></code> (run is ending) markers. Drives the <code class="docutils literal notranslate"><span class="pre">runState()</span></code> axis.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">ADARA::AnnotationPkt</span></code> — carries <code class="docutils literal notranslate"><span class="pre">PAUSE</span></code> and <code class="docutils literal notranslate"><span class="pre">RESUME</span></code> markers.
Drives the <code class="docutils literal notranslate"><span class="pre">isPaused()</span></code> axis.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">ADARA::BeamlineInfoPkt</span></code> and <code class="docutils literal notranslate"><span class="pre">ADARA::GeometryPkt</span></code>— carry instrument-geometry
information. Allow completion of workspace initialization.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">ADARA::BankedEventPkt</span></code> — carries the actual neutron-event
records. Gated on <code class="docutils literal notranslate"><span class="pre">m_isDasPaused</span></code> so paused events are discarded.</p></li>
</ul>
<section id="two-orthogonal-state-axes">
<h3><a class="toc-backref" href="#id3" role="doc-backlink">Two orthogonal state axes</a><a class="headerlink" href="#two-orthogonal-state-axes" title="Link to this heading">#</a></h3>
<p>The ADARA protocol exposes two independent state axes that the
legacy <code class="docutils literal notranslate"><span class="pre">runStatus()</span></code> conflated:</p>
<ul class="simple">
<li><p><strong>Run state</strong> — whether the DAS is in a run
(<code class="docutils literal notranslate"><span class="pre">NoRun</span> <span class="pre">/</span> <span class="pre">BeginRun</span> <span class="pre">/</span> <span class="pre">Running</span> <span class="pre">/</span> <span class="pre">EndRun</span></code>). Driven by
<code class="docutils literal notranslate"><span class="pre">RunStatusPkt</span></code>. Transitions require workspace-level coordination
(cache clears, re-init), so they are queued and committed in
<code class="docutils literal notranslate"><span class="pre">extractData()</span></code>.</p></li>
<li><p><strong>Pause state</strong> — whether the current run is paused. Driven by
<code class="docutils literal notranslate"><span class="pre">AnnotationPkt</span></code> <code class="docutils literal notranslate"><span class="pre">PAUSE</span></code> / <code class="docutils literal notranslate"><span class="pre">RESUME</span></code> markers. Orthogonal to
run state: a run can be in <code class="docutils literal notranslate"><span class="pre">Running</span></code> status while paused, and the
ADARA protocol does not change the <code class="docutils literal notranslate"><span class="pre">RunStatus</span></code> field on a pause.
Pause state has <strong>no workspace-level side effects</strong> beyond gating
event appending, so it is applied immediately in the background
thread, not queued.</p></li>
</ul>
<p>A summary of where each axis is read and written:</p>
<div class="pst-scrollable-table-container"><table class="table">
<colgroup>
<col style="width: 35.0%" />
<col style="width: 30.0%" />
<col style="width: 35.0%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Question</p></th>
<th class="head"><p>Answer</p></th>
<th class="head"><p>Type</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p>What is the DAS run state right now?</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">RunStatus</span></code></p></td>
<td><p>pure read (<code class="docutils literal notranslate"><span class="pre">runState()</span></code>)</p></td>
</tr>
<tr class="row-odd"><td><p>Is the current run paused?</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">bool</span></code></p></td>
<td><p>pure read (<code class="docutils literal notranslate"><span class="pre">isPaused()</span></code>)</p></td>
</tr>
<tr class="row-even"><td><p>Is the listener connected / back-pressure / errored?</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">ListenerState</span></code></p></td>
<td><p>pure read (<code class="docutils literal notranslate"><span class="pre">listenerState()</span></code>)</p></td>
</tr>
<tr class="row-odd"><td><p>What run-state transition (if any) did the most recent
<code class="docutils literal notranslate"><span class="pre">extractData()</span></code> consume?</p></td>
<td><p><code class="docutils literal notranslate"><span class="pre">optional<RunStatus></span></code></p></td>
<td><p>pure read (<code class="docutils literal notranslate"><span class="pre">lastTransition()</span></code>)</p></td>
</tr>
</tbody>
</table>
</div>
</section>
</section>
<section id="pre-refactor-problem">
<h2><a class="toc-backref" href="#id4" role="doc-backlink">Pre-refactor problem</a><a class="headerlink" href="#pre-refactor-problem" title="Link to this heading">#</a></h2>
<p>The legacy <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener::runStatus()</span></code> (roughly lines
1495–1534 in the pre-refactor <code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener.cpp</span></code>)
performed <em>all</em> of the following inside what callers treated as a
getter:</p>
<ul class="simple">
<li><p>Returned the cached <code class="docutils literal notranslate"><span class="pre">m_status</span></code> value.</p></li>
<li><p>On <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code>: consumed <code class="docutils literal notranslate"><span class="pre">m_deferredRunDetailsPkt</span></code> via
<code class="docutils literal notranslate"><span class="pre">setRunDetails(*m_deferredRunDetailsPkt)</span></code>, reset the deferred
packet, advanced <code class="docutils literal notranslate"><span class="pre">m_status</span></code> to <code class="docutils literal notranslate"><span class="pre">Running</span></code>.</p></li>
<li><p>On <code class="docutils literal notranslate"><span class="pre">EndRun</span></code>: cleared <code class="docutils literal notranslate"><span class="pre">m_instrumentXML</span></code>, <code class="docutils literal notranslate"><span class="pre">m_instrumentName</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_dataStartTime</span></code>, advanced <code class="docutils literal notranslate"><span class="pre">m_status</span></code> to <code class="docutils literal notranslate"><span class="pre">NoRun</span></code>.</p></li>
<li><p>Unconditionally: cleared the <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code> back-pressure flag,
releasing the background reader.</p></li>
<li><p>Implicitly: marked the workspace as needing re-initialisation and
ran <code class="docutils literal notranslate"><span class="pre">initWorkspacePart1()</span></code> to populate it.</p></li>
</ul>
<p>This produced four distinct problems:</p>
<ol class="arabic simple">
<li><p><strong>Conflated state.</strong> A single <code class="docutils literal notranslate"><span class="pre">RunStatus</span></code> return value reported
both the DAS run state and the listener’s internal FSM position.</p></li>
<li><p><strong>Hidden side effects.</strong> Every call to <code class="docutils literal notranslate"><span class="pre">runStatus()</span></code> mutated
caches and consumed packets. Reading the state without changing
it was impossible.</p></li>
<li><p><strong>The “little white lie” path (resolved by</strong> <code class="docutils literal notranslate"><span class="pre">JoiningRun</span></code> <strong>).</strong>
When <code class="docutils literal notranslate"><span class="pre">NEW_RUN</span></code> arrived before the workspace was initialised,
<code class="docutils literal notranslate"><span class="pre">rxPacket(RunStatusPkt)</span></code> skipped the <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> transition and
pretended the state was already <code class="docutils literal notranslate"><span class="pre">Running</span></code>, preserving the
workspace-init invariant but at the cost of an off-diagram code path
that was the only <code class="docutils literal notranslate"><span class="pre">NoRun</span> <span class="pre">→</span> <span class="pre">Running</span></code> transition committed outside
<code class="docutils literal notranslate"><span class="pre">onBeginRun()</span></code>. This is now eliminated: the listener instead
advances to the new <code class="docutils literal notranslate"><span class="pre">JoiningRun</span></code> state and queues a normal
<code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> edge once <code class="docutils literal notranslate"><span class="pre">initWorkspacePart2()</span></code> completes (see
<a class="reference internal" href="#joining-run-state"><span class="std std-ref">Single-slot invariant</span></a> below).</p></li>
<li><p><strong>Stand-alone ``LoadLiveData`` deadlock.</strong> <code class="docutils literal notranslate"><span class="pre">LoadLiveData</span></code> does
not call <code class="docutils literal notranslate"><span class="pre">runStatus()</span></code>. After <code class="docutils literal notranslate"><span class="pre">rxPacket(RunStatusPkt)</span></code> set
<code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span> <span class="pre">=</span> <span class="pre">true</span></code>, nothing ever cleared it. The
background reader stopped, the workspace could not be
re-initialised, and <code class="docutils literal notranslate"><span class="pre">extractData()</span></code> either timed out
(<code class="docutils literal notranslate"><span class="pre">NotYet</span></code>) or spun on a stale workspace.</p></li>
</ol>
<p>A fifth, more subtle issue: <code class="docutils literal notranslate"><span class="pre">MonitorLiveData</span></code> worked correctly <em>only
because</em> it happened to call <code class="docutils literal notranslate"><span class="pre">extractData()</span></code> first and
<code class="docutils literal notranslate"><span class="pre">runStatus()</span></code> second within each polling iteration. Nothing in
the type system enforced that ordering; it was an undocumented
contract.</p>
</section>
<section id="new-state-model">
<h2><a class="toc-backref" href="#id5" role="doc-backlink">New state model</a><a class="headerlink" href="#new-state-model" title="Link to this heading">#</a></h2>
<p>The conflated <code class="docutils literal notranslate"><span class="pre">m_status</span></code> member is split into three:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span></code> — what the DAS run state is right now (read by
<code class="docutils literal notranslate"><span class="pre">runState()</span></code>). Written by <code class="docutils literal notranslate"><span class="pre">rxPacket(RunStatusPkt)</span></code> and by the
<code class="docutils literal notranslate"><span class="pre">onBeginRun()</span></code> / <code class="docutils literal notranslate"><span class="pre">onEndRun()</span></code> hooks.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span></code> — exactly one run-state edge waiting to be
consumed by <code class="docutils literal notranslate"><span class="pre">extractData()</span></code> (queued by
<code class="docutils literal notranslate"><span class="pre">rxPacket(RunStatusPkt)</span></code>, dequeued by <code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code> for
<code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> and by <code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code> for <code class="docutils literal notranslate"><span class="pre">EndRun</span></code>).</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">m_lastTransition</span></code> — the run-state edge committed by the most
recent successful <code class="docutils literal notranslate"><span class="pre">extractData()</span></code> (read by <code class="docutils literal notranslate"><span class="pre">lastTransition()</span></code>).
Both <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> and <code class="docutils literal notranslate"><span class="pre">EndRun</span></code> are observable to the caller between
the <code class="docutils literal notranslate"><span class="pre">extractData()</span></code> that committed them and the next successful
<code class="docutils literal notranslate"><span class="pre">extractData()</span></code>.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">m_previousExtractCompleted</span></code> — set <code class="docutils literal notranslate"><span class="pre">true</span></code> at the end of
<code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code>, consumed (cleared) at the start of the next
<code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code> after both <code class="docutils literal notranslate"><span class="pre">NotYet</span></code> gates pass. Gates the
deferred clear of <code class="docutils literal notranslate"><span class="pre">m_lastTransition</span></code> so the edge survives
<code class="docutils literal notranslate"><span class="pre">Exception::NotYet</span></code> retries (the C1 invariant).</p></li>
</ul>
<p>The pause flag is renamed for clarity: <code class="docutils literal notranslate"><span class="pre">m_runPaused</span></code> →
<code class="docutils literal notranslate"><span class="pre">m_isDasPaused</span></code>, read by <code class="docutils literal notranslate"><span class="pre">isPaused()</span></code>.</p>
<p>The back-pressure flag <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code> keeps both its name and its
meaning, to keep the diff focused and the explanatory comments in
<code class="docutils literal notranslate"><span class="pre">rxPacket(RunStatusPkt)</span></code> intelligible.</p>
<section id="single-slot-invariant">
<span id="joining-run-state"></span><h3><a class="toc-backref" href="#id6" role="doc-backlink">Single-slot invariant</a><a class="headerlink" href="#single-slot-invariant" title="Link to this heading">#</a></h3>
<p>At most one un-consumed transition can exist at a time. The background
thread enforces this by setting <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span> <span class="pre">=</span> <span class="pre">true</span></code> whenever it
queues a transition; the background reader then blocks until
<code class="docutils literal notranslate"><span class="pre">extractData()</span></code> releases <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code> via the hook.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">JoiningRun</span></code> case (<code class="docutils literal notranslate"><span class="pre">NEW_RUN</span></code> with workspace not yet initialised)
is the one path that queues <em>no</em> back-pressure: the background thread
must keep reading packets to complete workspace initialisation. It
queues a <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> edge only once <code class="docutils literal notranslate"><span class="pre">initWorkspacePart2()</span></code> succeeds,
at which point <code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span></code> is not yet occupied and no
back-pressure is needed (there are no prior run’s events to protect).</p>
<p>The invariant is checked explicitly in <code class="docutils literal notranslate"><span class="pre">rxPacket(RunStatusPkt)</span></code>;
violation is treated as an implementation error and raises:</p>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_pendingTransition</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span>
<span class="w"> </span><span class="s">"SNSLiveEventDataListener: pending run-state transition was not "</span>
<span class="w"> </span><span class="s">"consumed before a new transition arrived — back-pressure invariant "</span>
<span class="w"> </span><span class="s">"violation."</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">m_pendingTransition</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BeginRun</span><span class="p">;</span><span class="w"> </span><span class="c1">// or EndRun</span>
<span class="n">m_pauseNetRead</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">true</span><span class="p">;</span>
</pre></div>
</div>
<p>This is a <code class="docutils literal notranslate"><span class="pre">runtime_error</span></code> rather than a debug-only assertion so the
condition is never silently masked in release builds.</p>
</section>
<section id="header-summary">
<h3><a class="toc-backref" href="#id7" role="doc-backlink">Header summary</a><a class="headerlink" href="#header-summary" title="Link to this heading">#</a></h3>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">SNSLiveEventDataListener</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="n">API</span><span class="o">::</span><span class="n">LiveListener</span><span class="p">,</span>
<span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="n">Poco</span><span class="o">::</span><span class="n">Runnable</span><span class="p">,</span>
<span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="n">ADARA</span><span class="o">::</span><span class="n">Parser</span><span class="w"> </span><span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
<span class="w"> </span><span class="c1">// pure queries</span>
<span class="w"> </span><span class="n">RunStatus</span><span class="w"> </span><span class="n">runState</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="nf">isPaused</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="n">ListenerState</span><span class="w"> </span><span class="nf">listenerState</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="n">RunStatus</span><span class="o">></span><span class="w"> </span><span class="n">lastTransition</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">runNumber</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="k">override</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">m_runNumber</span><span class="p">.</span><span class="n">load</span><span class="p">();</span><span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="nf">isConnected</span><span class="p">()</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="k">protected</span><span class="o">:</span>
<span class="w"> </span><span class="c1">/// Phase 1 of extraction: commit any queued ``BeginRun`` transition.</span>
<span class="w"> </span><span class="c1">/// Called by LiveListener::extractData() before doExtractData().</span>
<span class="w"> </span><span class="c1">/// Runs on the foreground thread.</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="n">onBeforeExtract</span><span class="p">()</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="c1">/// Phase 2/3 of extraction: wait for workspace initialisation,</span>
<span class="w"> </span><span class="c1">/// build the new EventWorkspace, swap, and return it.</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">API</span><span class="o">::</span><span class="n">Workspace</span><span class="o">></span><span class="w"> </span><span class="n">doExtractData</span><span class="p">()</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="c1">/// Phase 4 of extraction: commit any queued ``EndRun`` transition.</span>
<span class="w"> </span><span class="c1">/// Deferred to after doExtractData() so the finishing run's</span>
<span class="w"> </span><span class="c1">/// accumulated events are harvested before the workspace buffer</span>
<span class="w"> </span><span class="c1">/// is reset by onEndRun().</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onAfterExtract</span><span class="p">()</span><span class="w"> </span><span class="k">override</span><span class="p">;</span>
<span class="w"> </span><span class="c1">/// Sends all bytes in buf over m_socket, retrying short writes.</span>
<span class="w"> </span><span class="c1">/// Virtual so test subclasses can inject short-write/failure scenarios.</span>
<span class="w"> </span><span class="k">virtual</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="nf">sendHelloPacket</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">size</span><span class="p">);</span>
<span class="w"> </span><span class="c1">/// Explicit, named state-transition hooks.</span>
<span class="w"> </span><span class="k">virtual</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onBeginRun</span><span class="p">();</span>
<span class="w"> </span><span class="k">virtual</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onEndRun</span><span class="p">();</span>
<span class="w"> </span><span class="k">virtual</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onRunPause</span><span class="p">(</span><span class="kt">bool</span><span class="w"> </span><span class="n">paused</span><span class="p">);</span>
<span class="k">private</span><span class="o">:</span>
<span class="w"> </span><span class="c1">// ADARA / DAS state (written by background thread)</span>
<span class="w"> </span><span class="n">RunStatus</span><span class="w"> </span><span class="n">m_adaraRunStatus</span><span class="p">{</span><span class="n">NoRun</span><span class="p">};</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">ADARA</span><span class="o">::</span><span class="n">RunStatusPkt</span><span class="o">></span><span class="w"> </span><span class="n">m_deferredRunDetailsPkt</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Pending run-state transition (background -> foreground).</span>
<span class="w"> </span><span class="c1">// INVARIANT: at most one un-consumed transition at a time.</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="n">RunStatus</span><span class="o">></span><span class="w"> </span><span class="n">m_pendingTransition</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Result of the most recent commit (read by lastTransition()).</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="n">RunStatus</span><span class="o">></span><span class="w"> </span><span class="n">m_lastTransition</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Listener health</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="o">></span><span class="w"> </span><span class="n">m_backgroundException</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Existing members (unchanged)</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">int</span><span class="o">></span><span class="w"> </span><span class="n">m_runNumber</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="w"> </span><span class="n">DataObjects</span><span class="o">::</span><span class="n">EventWorkspace_sptr</span><span class="w"> </span><span class="n">m_eventBuffer</span><span class="p">;</span>
<span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">m_workspaceInitialized</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="w"> </span><span class="n">m_instrumentName</span><span class="p">;</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="w"> </span><span class="n">m_instrumentXML</span><span class="p">;</span>
<span class="w"> </span><span class="cm">/* ... etc. ... */</span>
<span class="w"> </span><span class="k">mutable</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="w"> </span><span class="n">m_mutex</span><span class="p">;</span><span class="w"> </span><span class="c1">///< guards all of the above</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="w"> </span><span class="n">m_isConnected</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span><span class="w"> </span><span class="c1">///< connection health</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="w"> </span><span class="n">m_pauseNetRead</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span><span class="w"> </span><span class="c1">///< back-pressure flag</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="w"> </span><span class="n">m_bgThreadCaughtUp</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span><span class="w"> </span><span class="c1">///< foreground snapshot gate</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="w"> </span><span class="n">m_stopThread</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span><span class="w"> </span><span class="c1">///< destructor shutdown signal</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="w"> </span><span class="n">m_isDasPaused</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span><span class="w"> </span><span class="c1">///< set by onRunPause(); read by isPaused()</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="w"> </span><span class="n">m_ignorePackets</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span><span class="w"> </span><span class="c1">///< see ignorePacket() / the m_ignorePackets appendix</span>
<span class="w"> </span><span class="n">NameMapType</span><span class="w"> </span><span class="n">m_nameMap</span><span class="p">;</span>
<span class="w"> </span><span class="cm">/* ... etc. ... */</span>
<span class="p">};</span>
</pre></div>
</div>
<p>Convention: any POD-typed member that a background thread and the
foreground both touch is made <code class="docutils literal notranslate"><span class="pre">std::atomic</span></code> even when it is already
covered by <code class="docutils literal notranslate"><span class="pre">m_mutex</span></code> — belt-and-suspenders rather than relying on the
lock alone. Concretely, per the header’s own grouping
(<code class="docutils literal notranslate"><span class="pre">SNSLiveEventDataListener.h:242-270</span></code>):</p>
<ul class="simple">
<li><p><strong>Atomic, lock-free (no mutex needed):</strong> <code class="docutils literal notranslate"><span class="pre">m_isConnected</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code>, <code class="docutils literal notranslate"><span class="pre">m_bgThreadCaughtUp</span></code>, <code class="docutils literal notranslate"><span class="pre">m_stopThread</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_isDasPaused</span></code>, <code class="docutils literal notranslate"><span class="pre">m_runNumber</span></code>.</p></li>
<li><p><strong>Atomic AND mutex-guarded:</strong> <code class="docutils literal notranslate"><span class="pre">m_ignorePackets</span></code> — guarded by the
single <code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code> critical section on the foreground side and
by the parse lock on the background side (<code class="docutils literal notranslate"><span class="pre">ignorePacket()</span></code>).</p></li>
<li><p><strong>Mutex-guarded only:</strong> <code class="docutils literal notranslate"><span class="pre">m_backgroundException</span></code>, <code class="docutils literal notranslate"><span class="pre">m_eventBuffer</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span></code>, <code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span></code>, <code class="docutils literal notranslate"><span class="pre">m_lastTransition</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_instrumentXML</span></code>, <code class="docutils literal notranslate"><span class="pre">m_instrumentName</span></code>, <code class="docutils literal notranslate"><span class="pre">m_nameMap</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_requiredLogs</span></code>, <code class="docutils literal notranslate"><span class="pre">m_deferredRunDetailsPkt</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_workspaceInitialized</span></code>, <code class="docutils literal notranslate"><span class="pre">m_previousExtractCompleted</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_dataStartTime</span></code>, <code class="docutils literal notranslate"><span class="pre">m_monitorLogs</span></code>, <code class="docutils literal notranslate"><span class="pre">m_wsName</span></code>.</p></li>
</ul>
<p>All <code class="docutils literal notranslate"><span class="pre">rxPacket()</span></code> overrides now run under <code class="docutils literal notranslate"><span class="pre">m_mutex</span></code>, acquired once by
<code class="docutils literal notranslate"><span class="pre">run()</span></code> around the whole <code class="docutils literal notranslate"><span class="pre">bufferParse()</span></code> iteration (see “Background
reader loop” below) — do <strong>not</strong> acquire <code class="docutils literal notranslate"><span class="pre">m_mutex</span></code> inside any
<code class="docutils literal notranslate"><span class="pre">rxPacket()</span></code> body or any function called exclusively from one;
<code class="docutils literal notranslate"><span class="pre">std::mutex</span></code> is non-recursive and a second acquisition would deadlock.</p>
</section>
</section>
<section id="state-machine-diagrams">
<h2><a class="toc-backref" href="#id8" role="doc-backlink">State-machine diagrams</a><a class="headerlink" href="#state-machine-diagrams" title="Link to this heading">#</a></h2>
<p>Two diagrams below show the <code class="docutils literal notranslate"><span class="pre">runState</span></code> evolution for the SNS
listener. Apart from the addition of a new <code class="docutils literal notranslate"><span class="pre">JoiningRun</span></code> state,
the shape of the state machine is unchanged between
pre- and post-refactor; what changes is <strong>where the transitions are
committed</strong> — and that change is what makes the listener safe to use
from stand-alone <code class="docutils literal notranslate"><span class="pre">LoadLiveData</span></code>.</p>
<p>In both diagrams:</p>
<ul class="simple">
<li><p>Nodes are the four <code class="docutils literal notranslate"><span class="pre">runState</span></code> values (<code class="docutils literal notranslate"><span class="pre">NoRun</span></code>, <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code>,
<code class="docutils literal notranslate"><span class="pre">Running</span></code>, <code class="docutils literal notranslate"><span class="pre">EndRun</span></code>).</p></li>
<li><p>Edges are labelled with the ADARA packet (or call) that triggers
the transition.</p></li>
<li><p>A dashed box (cluster) encloses the transitions that are
<strong>committed at a particular point in the code</strong> — i.e. where the
visible state mutation and the workspace-level side effects
actually occur.</p></li>
<li><p>The <code class="docutils literal notranslate"><span class="pre">isPaused()</span></code> axis is shown as a separate small subgraph; it
is orthogonal to <code class="docutils literal notranslate"><span class="pre">runState</span></code> and applied immediately in the
background thread.</p></li>
<li><p>The <code class="docutils literal notranslate"><span class="pre">listenerState()</span></code> axis is shown as a separate small subgraph. In
the post-refactor diagram it additionally carries the
<code class="docutils literal notranslate"><span class="pre">Connected</span></code> → <code class="docutils literal notranslate"><span class="pre">Disconnected</span></code> edge for a clean peer close, the
getter’s <code class="docutils literal notranslate"><span class="pre">Error</span></code> > <code class="docutils literal notranslate"><span class="pre">Disconnected</span></code> > <code class="docutils literal notranslate"><span class="pre">ReadWait</span></code> > <code class="docutils literal notranslate"><span class="pre">Connected</span></code>
precedence, and a dotted edge marking that the <code class="docutils literal notranslate"><span class="pre">Error</span></code> outcome is a
<em>foreground</em> escalation, not something the background thread itself
ascribes — see <a class="reference internal" href="#sns-disconnect-not-error"><span class="std std-ref">Disconnect is not an error — and where the error gets ascribed</span></a>.</p></li>
</ul>
<section id="pre-refactor-transitions-committed-inside-runstatus">
<h3><a class="toc-backref" href="#id9" role="doc-backlink">Pre-refactor: transitions committed inside <code class="docutils literal notranslate"><span class="pre">runStatus()</span></code></a><a class="headerlink" href="#pre-refactor-transitions-committed-inside-runstatus" title="Link to this heading">#</a></h3>
<img alt="SNSLiveEventDataListener state machine, pre-refactor. BeginRun→Running and EndRun→NoRun edges are enclosed in a dashed cluster labelled "committed inside runStatus()", indicating that the cache clears, workspace re-init, deferred RunStatusPkt consumption, and m_pauseNetRead release all happen as side effects of the runStatus() getter." class="align-center" src="_images/SNSLiveEventDataListener-state-machine-before.png" />
<p>In the pre-refactor design the commit boundary is <strong>the
``runStatus()`` getter itself</strong>. External callers that never invoke
<code class="docutils literal notranslate"><span class="pre">runStatus()</span></code> — notably stand-alone <code class="docutils literal notranslate"><span class="pre">LoadLiveData</span></code> — never trigger
the transition, <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code> is never released, and the
listener deadlocks.</p>
<p>The diagram source is committed alongside the rendered image at
<code class="docutils literal notranslate"><span class="pre">dev-docs/source/images/SNSLiveEventDataListener-state-machine-before.dot</span></code>;
regenerate the PNG with:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">dot</span> <span class="o">-</span><span class="n">Tpng</span> <span class="o">-</span><span class="n">o</span> <span class="n">SNSLiveEventDataListener</span><span class="o">-</span><span class="n">state</span><span class="o">-</span><span class="n">machine</span><span class="o">-</span><span class="n">before</span><span class="o">.</span><span class="n">png</span> \
<span class="n">SNSLiveEventDataListener</span><span class="o">-</span><span class="n">state</span><span class="o">-</span><span class="n">machine</span><span class="o">-</span><span class="n">before</span><span class="o">.</span><span class="n">dot</span>
</pre></div>
</div>
</section>
<section id="post-refactor-transitions-committed-inside-extractdata">
<h3><a class="toc-backref" href="#id10" role="doc-backlink">Post-refactor: transitions committed inside <code class="docutils literal notranslate"><span class="pre">extractData()</span></code></a><a class="headerlink" href="#post-refactor-transitions-committed-inside-extractdata" title="Link to this heading">#</a></h3>
<img alt="SNSLiveEventDataListener state machine, post-refactor. BeginRun→Running and EndRun→NoRun edges are enclosed in a dashed cluster labelled "committed inside extractData() (onBeforeExtract → doExtractData → onAfterExtract)", indicating that the cache clears, workspace re-init, deferred-packet consumption, and m_pauseNetRead release all happen as part of the template-method extractData() call — with BeginRun dispatched in onBeforeExtract() and EndRun deferred to onAfterExtract() so the finishing run's events are harvested first. The listenerState axis additionally shows a Connected→Disconnected edge for a clean peer close (the background thread stays neutral: no fatal(), no m_backgroundException) and a dotted Disconnected→Error edge marking that the Error outcome is ascribed by the foreground (doExtractData(), which has no recovery action available), not by the background thread." class="align-center" src="_images/SNSLiveEventDataListener-state-machine-after.png" />
<p>In the post-refactor design the commit boundary is <strong>the
``extractData()`` template method</strong> — specifically the
<code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code> and <code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code> hooks that bracket
<code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code>. <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code> is dispatched in
<code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code> so the new run’s workspace is initialised
before <code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code> snapshots it; <code class="docutils literal notranslate"><span class="pre">EndRun</span></code> is dispatched in
<code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code> so the finishing run’s accumulated events are
harvested before <code class="docutils literal notranslate"><span class="pre">onEndRun()</span></code> resets the buffer.
Because every consumer of <code class="docutils literal notranslate"><span class="pre">ILiveListener</span></code> calls <code class="docutils literal notranslate"><span class="pre">extractData()</span></code>,
every consumer drives the FSM forward, and stand-alone
<code class="docutils literal notranslate"><span class="pre">LoadLiveData</span></code> works without changes.</p>
<p>The diagram source is committed alongside the rendered image at
<code class="docutils literal notranslate"><span class="pre">dev-docs/source/images/SNSLiveEventDataListener-state-machine-after.dot</span></code>;
regenerate the PNG with the analogous <code class="docutils literal notranslate"><span class="pre">dot</span></code> command.</p>
</section>
</section>
<section id="implementation">
<h2><a class="toc-backref" href="#id11" role="doc-backlink">Implementation</a><a class="headerlink" href="#implementation" title="Link to this heading">#</a></h2>
<section id="pure-getters">
<h3><a class="toc-backref" href="#id12" role="doc-backlink">Pure getters</a><a class="headerlink" href="#pure-getters" title="Link to this heading">#</a></h3>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="n">ILiveListener</span><span class="o">::</span><span class="n">RunStatus</span>
<span class="nf">SNSLiveEventDataListener::runState</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_backgroundException</span><span class="p">)</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="o">*</span><span class="n">m_backgroundException</span><span class="p">;</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="n">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">m_adaraRunStatus</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ListenerState</span>
<span class="nf">SNSLiveEventDataListener::listenerState</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="n">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_backgroundException</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">ListenerState</span><span class="o">::</span><span class="n">Error</span><span class="p">;</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">m_isConnected</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">ListenerState</span><span class="o">::</span><span class="n">Disconnected</span><span class="p">;</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_pauseNetRead</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">ListenerState</span><span class="o">::</span><span class="n">ReadWait</span><span class="p">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">ListenerState</span><span class="o">::</span><span class="n">Connected</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="n">ILiveListener</span><span class="o">::</span><span class="n">RunStatus</span><span class="o">></span>
<span class="n">SNSLiveEventDataListener</span><span class="o">::</span><span class="n">lastTransition</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_backgroundException</span><span class="p">)</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="o">*</span><span class="n">m_backgroundException</span><span class="p">;</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">m_lastTransition</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">bool</span><span class="w"> </span><span class="n">SNSLiveEventDataListener</span><span class="o">::</span><span class="n">isPaused</span><span class="p">()</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">m_isDasPaused</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
</div>
<p>All four getters are <code class="docutils literal notranslate"><span class="pre">const</span></code> and have no side effects. None mutate
<code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span></code>, <code class="docutils literal notranslate"><span class="pre">m_isDasPaused</span></code>, <code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span></code>,
<code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code>, or any cache.</p>
</section>
<section id="background-reader-and-packet-handlers">
<h3><a class="toc-backref" href="#id13" role="doc-backlink">Background reader and packet handlers</a><a class="headerlink" href="#background-reader-and-packet-handlers" title="Link to this heading">#</a></h3>
<p>In <code class="docutils literal notranslate"><span class="pre">rxPacket(const</span> <span class="pre">ADARA::RunStatusPkt</span> <span class="pre">&pkt)</span></code> the changes versus the
legacy implementation are:</p>
<ul class="simple">
<li><p>Replace assignments to the conflated <code class="docutils literal notranslate"><span class="pre">m_status</span></code> with assignments
to <code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span></code>.</p></li>
<li><p>On <code class="docutils literal notranslate"><span class="pre">NEW_RUN</span></code> with <code class="docutils literal notranslate"><span class="pre">m_workspaceInitialized</span> <span class="pre">==</span> <span class="pre">true</span></code>: enforce the
single-slot invariant, queue <code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span> <span class="pre">=</span> <span class="pre">BeginRun</span></code>,
set <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span> <span class="pre">=</span> <span class="pre">true</span></code>.</p></li>
<li><p>On <code class="docutils literal notranslate"><span class="pre">NEW_RUN</span></code> with <code class="docutils literal notranslate"><span class="pre">m_workspaceInitialized</span> <span class="pre">==</span> <span class="pre">false</span></code> (joining path):
set <code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span> <span class="pre">=</span> <span class="pre">JoiningRun</span></code>, call <code class="docutils literal notranslate"><span class="pre">setRunDetails(pkt)</span></code>
immediately so <code class="docutils literal notranslate"><span class="pre">run_number</span></code> / <code class="docutils literal notranslate"><span class="pre">run_start</span></code> are available during the
bootstrap window, queue <strong>no</strong> transition, set <strong>no</strong> back-pressure.
The background thread must keep reading to complete workspace init.
Once <code class="docutils literal notranslate"><span class="pre">initWorkspacePart2()</span></code> sets <code class="docutils literal notranslate"><span class="pre">m_workspaceInitialized</span> <span class="pre">=</span> <span class="pre">true</span></code>
and sees <code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span> <span class="pre">==</span> <span class="pre">JoiningRun</span></code>, it queues
<code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span> <span class="pre">=</span> <span class="pre">BeginRun</span></code>; the foreground then commits to
<code class="docutils literal notranslate"><span class="pre">Running</span></code> via the normal <code class="docutils literal notranslate"><span class="pre">onBeginRun()</span></code> hook (joining-completion
path: no cache reset, no <code class="docutils literal notranslate"><span class="pre">m_deferredRunDetailsPkt</span></code> needed).</p></li>
<li><p>On <code class="docutils literal notranslate"><span class="pre">END_RUN</span></code>: enforce the single-slot invariant, queue
<code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span> <span class="pre">=</span> <span class="pre">EndRun</span></code>, set <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span> <span class="pre">=</span> <span class="pre">true</span></code>,
copy <code class="docutils literal notranslate"><span class="pre">setRunDetails(pkt)</span></code> if <code class="docutils literal notranslate"><span class="pre">!haveRunNumber</span></code> (unchanged).
<code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span></code> is <strong>not</strong> mutated here — it is advanced to
<code class="docutils literal notranslate"><span class="pre">NoRun</span></code> later from <code class="docutils literal notranslate"><span class="pre">onEndRun()</span></code> (dispatched from
<code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code> after <code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code> has harvested the
finishing run’s events). Until that commit, <code class="docutils literal notranslate"><span class="pre">runState()</span></code> continues
to report <code class="docutils literal notranslate"><span class="pre">Running</span></code> (or <code class="docutils literal notranslate"><span class="pre">JoiningRun</span></code>); the <code class="docutils literal notranslate"><span class="pre">EndRun</span></code> edge is
delivered to consumers via <code class="docutils literal notranslate"><span class="pre">lastTransition()</span></code>, satisfying the
“exactly once” delivery contract.</p></li>
</ul>
<p>In <code class="docutils literal notranslate"><span class="pre">rxPacket(const</span> <span class="pre">ADARA::AnnotationPkt</span> <span class="pre">&pkt)</span></code>, <code class="docutils literal notranslate"><span class="pre">PAUSE</span></code> and
<code class="docutils literal notranslate"><span class="pre">RESUME</span></code> markers call <code class="docutils literal notranslate"><span class="pre">onRunPause(true/false)</span></code> directly from the
background thread. This is intentional: pause state has no
workspace-level side effects (it only gates event appending in
<code class="docutils literal notranslate"><span class="pre">rxPacket(BankedEventPkt)</span></code>), so applying it immediately at the
packet boundary gives the most accurate event filtering relative to
the DAS timeline. The pause/resume path does <strong>not</strong> use
<code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span></code> and does <strong>not</strong> set <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code>.</p>
</section>
<section id="foreground-snapshot-gate-m-bgthreadcaughtup">
<h3><a class="toc-backref" href="#id14" role="doc-backlink">Foreground snapshot gate (<code class="docutils literal notranslate"><span class="pre">m_bgThreadCaughtUp</span></code>)</a><a class="headerlink" href="#foreground-snapshot-gate-m-bgthreadcaughtup" title="Link to this heading">#</a></h3>
<p>A new <code class="docutils literal notranslate"><span class="pre">std::atomic<bool></span> <span class="pre">m_bgThreadCaughtUp{false}</span></code> field
synchronises the background parser and the foreground extractor:</p>
<ul class="simple">
<li><p><strong>Background writes:</strong> stored <code class="docutils literal notranslate"><span class="pre">false</span></code> at the start of every
<code class="docutils literal notranslate"><span class="pre">bufferParse()</span></code> iteration; stored <code class="docutils literal notranslate"><span class="pre">true</span></code> at the end.
<code class="docutils literal notranslate"><span class="pre">receiveBytes()</span></code> does <strong>not</strong> touch the flag — a background thread
blocked in receive presents as “caught-up” because its in-memory
state is stable.</p></li>
<li><p><strong>Foreground use in</strong> <code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code>: if
<code class="docutils literal notranslate"><span class="pre">m_thread.isRunning()</span> <span class="pre">&&</span> <span class="pre">!m_bgThreadCaughtUp</span></code>, the call throws
<code class="docutils literal notranslate"><span class="pre">Exception::NotYet</span></code> immediately, without touching any shared state.
This closes the inter-<code class="docutils literal notranslate"><span class="pre">rxPacket()</span></code> race window — without it, the
foreground could snapshot <code class="docutils literal notranslate"><span class="pre">m_pendingTransition</span></code> between two
<code class="docutils literal notranslate"><span class="pre">rxPacket()</span></code> calls in the same <code class="docutils literal notranslate"><span class="pre">bufferParse()</span></code> iteration.</p></li>
<li><p><strong>Background use in the pause loop:</strong> the inner pause loop
short-circuits on <code class="docutils literal notranslate"><span class="pre">m_bgThreadCaughtUp</span> <span class="pre">==</span> <span class="pre">false</span></code> so the foreground
cannot wake the background thread (by clearing <code class="docutils literal notranslate"><span class="pre">m_pauseNetRead</span></code>)
while a parse is still in flight.</p></li>
<li><p><strong>Run-boundary reset:</strong> <code class="docutils literal notranslate"><span class="pre">onEndRun()</span></code> stores <code class="docutils literal notranslate"><span class="pre">false</span></code> immediately
before setting <code class="docutils literal notranslate"><span class="pre">m_adaraRunStatus</span> <span class="pre">=</span> <span class="pre">NoRun</span></code>. This re-arms the
first-extract synchronisation for every subsequent run, exactly
mirroring the construction-time initial value: the first
<code class="docutils literal notranslate"><span class="pre">extractData()</span></code> of each run is gated on that run’s first
<code class="docutils literal notranslate"><span class="pre">bufferParse()</span></code>, not just the very first run.</p></li>
<li><p><strong>Test-fixture note:</strong> <code class="docutils literal notranslate"><span class="pre">NoNetworkTest</span></code> fixtures run with no
background thread, so <code class="docutils literal notranslate"><span class="pre">m_thread.isRunning()</span></code> short-circuits the
guard for them; no fake flag manipulation is required in tests.</p></li>
</ul>
</section>
<section id="extractdata-the-only-commit-point">
<h3><a class="toc-backref" href="#id15" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">extractData()</span></code> — the only commit point</a><a class="headerlink" href="#extractdata-the-only-commit-point" title="Link to this heading">#</a></h3>
<p>The body below is presented as a single <code class="docutils literal notranslate"><span class="pre">extractData()</span></code> for clarity.
In the actual implementation it is split across the three-phase
<code class="docutils literal notranslate"><span class="pre">LiveListener::extractData()</span></code> template method
(<code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code> → <code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code> → <code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code>),
with the <code class="docutils literal notranslate"><span class="pre">m_backgroundException</span></code> rethrow remaining the
responsibility of the individual SNS getters (<code class="docutils literal notranslate"><span class="pre">runState()</span></code>,
<code class="docutils literal notranslate"><span class="pre">lastTransition()</span></code> — see the pure-getters section above).
<code class="docutils literal notranslate"><span class="pre">onBeforeExtract()</span></code> handles Phase 1 (commit a pending <code class="docutils literal notranslate"><span class="pre">BeginRun</span></code>
edge); <code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code> handles Phase 2/3 (workspace-init wait +
<code class="docutils literal notranslate"><span class="pre">EventWorkspace</span></code> build + swap); <code class="docutils literal notranslate"><span class="pre">onAfterExtract()</span></code> handles Phase 4
(commit a pending <code class="docutils literal notranslate"><span class="pre">EndRun</span></code> edge, deferred until after
<code class="docutils literal notranslate"><span class="pre">doExtractData()</span></code> so the finishing run’s events are harvested first).
The exception-safety guarantees described below survive the split
unchanged.</p>
<div class="highlight-cpp notranslate"><div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">Workspace</span><span class="o">></span>
<span class="n">SNSLiveEventDataListener</span><span class="o">::</span><span class="n">extractData</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="c1">// conceptually</span>
<span class="w"> </span><span class="c1">// m_backgroundException is re-thrown in the individual SNS getters</span>
<span class="w"> </span><span class="c1">// (runState(), lastTransition()), not in this template method body.</span>
<span class="w"> </span><span class="c1">// Shown here for orientation; the base LiveListener::extractData() body</span>
<span class="w"> </span><span class="c1">// is simply: onBeforeExtract() → doExtractData() → onAfterExtract().</span>
<span class="w"> </span><span class="c1">// ---- Phase 1 (onBeforeExtract): commit a pending BeginRun --------</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Fast-path: throw NotYet immediately if the background thread is</span>
<span class="w"> </span><span class="c1">// mid-parse (before taking any lock).</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_thread</span><span class="p">.</span><span class="n">isRunning</span><span class="p">()</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">!</span><span class="n">m_bgThreadCaughtUp</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">))</span>
<span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">Exception</span><span class="o">::</span><span class="n">NotYet</span><span class="p">(</span><span class="s">"Background thread parse is in flight."</span><span class="p">);</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="n">RunStatus</span><span class="o">></span><span class="w"> </span><span class="n">pending</span><span class="p">;</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Re-check under the lock to close the TOCTOU window.</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_thread</span><span class="p">.</span><span class="n">isRunning</span><span class="p">()</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">!</span><span class="n">m_bgThreadCaughtUp</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">))</span>
<span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">Exception</span><span class="o">::</span><span class="n">NotYet</span><span class="p">(</span><span class="s">"Background thread parse is in flight."</span><span class="p">);</span>
<span class="w"> </span><span class="c1">// Both NotYet gates passed: the prior extract's edge has been</span>
<span class="w"> </span><span class="c1">// delivered. Clear it now so the next tick sees nullopt.</span>
<span class="w"> </span><span class="c1">// m_previousExtractCompleted is false during a NotYet retry, so</span>
<span class="w"> </span><span class="c1">// the edge survives retries (C1 invariant).</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_previousExtractCompleted</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">m_lastTransition</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
<span class="w"> </span><span class="n">m_previousExtractCompleted</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">false</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="n">pending</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">m_pendingTransition</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pending</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">*</span><span class="n">pending</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">BeginRun</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="n">m_pendingTransition</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="n">onBeginRun</span><span class="p">();</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="n">m_lastTransition</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BeginRun</span><span class="p">;</span><span class="w"> </span><span class="c1">// memoise for lastTransition()</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="n">m_pauseNetRead</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">false</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// ---- Phase 2: wait for workspace initialisation (unchanged) -----</span>
<span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="n">maxBlockTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">10.0</span><span class="p">;</span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="n">DateAndTime</span><span class="w"> </span><span class="n">endTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DateAndTime</span><span class="o">::</span><span class="n">getCurrentTime</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">maxBlockTime</span><span class="p">;</span>
<span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">m_workspaceInitialized</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="n">DateAndTime</span><span class="o">::</span><span class="n">getCurrentTime</span><span class="p">()</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">endTime</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Poco</span><span class="o">::</span><span class="n">Thread</span><span class="o">::</span><span class="n">sleep</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">m_workspaceInitialized</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">Exception</span><span class="o">::</span><span class="n">NotYet</span><span class="p">(</span><span class="s">"The workspace has not yet been initialized."</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">m_ignorePackets</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">Exception</span><span class="o">::</span><span class="n">NotYet</span><span class="p">(</span><span class="s">"Waiting for a run to start."</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// ---- Phase 3: build the new EventWorkspace and swap (unchanged) -</span>
<span class="w"> </span><span class="n">EventWorkspace_sptr</span><span class="w"> </span><span class="n">temp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">dynamic_pointer_cast</span><span class="o"><</span><span class="n">EventWorkspace</span><span class="o">></span><span class="p">(</span>
<span class="w"> </span><span class="n">API</span><span class="o">::</span><span class="n">WorkspaceFactory</span><span class="o">::</span><span class="n">Instance</span><span class="p">().</span><span class="n">create</span><span class="p">(</span>
<span class="w"> </span><span class="s">"EventWorkspace"</span><span class="p">,</span><span class="w"> </span><span class="n">m_eventBuffer</span><span class="o">-></span><span class="n">getNumberHistograms</span><span class="p">(),</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">));</span>
<span class="w"> </span><span class="n">API</span><span class="o">::</span><span class="n">WorkspaceFactory</span><span class="o">::</span><span class="n">Instance</span><span class="p">().</span><span class="n">initializeFromParent</span><span class="p">(</span><span class="o">*</span><span class="n">m_eventBuffer</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="n">temp</span><span class="p">,</span><span class="w"> </span><span class="nb">false</span><span class="p">);</span>
<span class="w"> </span><span class="n">temp</span><span class="o">-></span><span class="n">mutableRun</span><span class="p">().</span><span class="n">clearOutdatedTimeSeriesLogValues</span><span class="p">();</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="k">auto</span><span class="w"> </span><span class="o">&</span><span class="n">monitorLog</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">m_monitorLogs</span><span class="p">)</span>
<span class="w"> </span><span class="n">temp</span><span class="o">-></span><span class="n">mutableRun</span><span class="p">().</span><span class="n">removeProperty</span><span class="p">(</span><span class="n">monitorLog</span><span class="p">);</span>
<span class="w"> </span><span class="n">m_monitorLogs</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
<span class="w"> </span><span class="k">auto</span><span class="w"> </span><span class="n">monitorBuffer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">m_eventBuffer</span><span class="o">-></span><span class="n">monitorWorkspace</span><span class="p">();</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">monitorBuffer</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">auto</span><span class="w"> </span><span class="n">newMonitorBuffer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">WorkspaceFactory</span><span class="o">::</span><span class="n">Instance</span><span class="p">().</span><span class="n">create</span><span class="p">(</span>
<span class="w"> </span><span class="s">"EventWorkspace"</span><span class="p">,</span><span class="w"> </span><span class="n">monitorBuffer</span><span class="o">-></span><span class="n">getNumberHistograms</span><span class="p">(),</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span>
<span class="w"> </span><span class="n">WorkspaceFactory</span><span class="o">::</span><span class="n">Instance</span><span class="p">().</span><span class="n">initializeFromParent</span><span class="p">(</span>
<span class="w"> </span><span class="o">*</span><span class="n">monitorBuffer</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="n">newMonitorBuffer</span><span class="p">,</span><span class="w"> </span><span class="nb">false</span><span class="p">);</span>
<span class="w"> </span><span class="n">temp</span><span class="o">-></span><span class="n">setMonitorWorkspace</span><span class="p">(</span><span class="n">newMonitorBuffer</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">swap</span><span class="p">(</span><span class="n">m_eventBuffer</span><span class="p">,</span><span class="w"> </span><span class="n">temp</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// ---- Phase 4 (onAfterExtract): commit a pending EndRun ----------</span>
<span class="w"> </span><span class="c1">// Deferred so the snapshot above contains the finishing run's</span>
<span class="w"> </span><span class="c1">// accumulated events before onEndRun() resets the buffer.</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="n">RunStatus</span><span class="o">></span><span class="w"> </span><span class="n">pending</span><span class="p">;</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="n">pending</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">m_pendingTransition</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// m_lastTransition is NOT cleared here; it is cleared at the</span>
<span class="w"> </span><span class="c1">// start of the next onBeforeExtract() after both NotYet gates</span>
<span class="w"> </span><span class="c1">// pass, guarded by m_previousExtractCompleted.</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pending</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">*</span><span class="n">pending</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">EndRun</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="w"> </span><span class="nf">lock</span><span class="p">(</span><span class="n">m_mutex</span><span class="p">);</span>
<span class="w"> </span><span class="n">m_pendingTransition</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
<span class="w"> </span><span class="n">m_lastTransition</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">EndRun</span><span class="p">;</span><span class="w"> </span><span class="c1">// memoise for lastTransition()</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="n">onEndRun</span><span class="p">();</span>
<span class="w"> </span><span class="n">m_pauseNetRead</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">false</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>