1 #include "artdaq/DAQdata/Globals.hh"
2 #include "artdaq/DAQrate/EventStore.hh"
12 #include "cetlib/exception.h"
13 #include "artdaq-core/Core/StatisticsCollection.hh"
14 #include "artdaq-core/Core/SimpleQueueReader.hh"
15 #include "artdaq/Application/Routing/RoutingPacket.hh"
22 const std::string EventStore::EVENT_RATE_STAT_KEY(
"EventStoreEventRate");
23 const std::string EventStore::INCOMPLETE_EVENT_STAT_KEY(
"EventStoreIncompleteEvents");
25 EventStore::EventStore(
const fhicl::ParameterSet& pset,
size_t num_fragments_per_event,
run_id_t run,
26 size_t event_queue_depth,
size_t max_incomplete_event_count)
27 : num_fragments_per_event_(num_fragments_per_event)
28 , max_queue_size_(pset.get<size_t>(
"event_queue_depth", event_queue_depth))
29 , max_incomplete_count_(pset.get<size_t>(
"max_incomplete_events", max_incomplete_event_count))
33 , queue_(getGlobalQueue(max_queue_size_))
34 , reader_thread_launch_time_(std::chrono::steady_clock::now())
35 , send_requests_(pset.get<bool>(
"send_requests", false))
37 , request_port_(pset.get<int>(
"request_port", 3001))
38 , request_delay_(pset.get<size_t>(
"request_delay_ms", 10))
39 , multicast_out_addr_(pset.get<std::string>(
"output_address",
"localhost"))
40 , request_mode_(detail::RequestMessageMode::Normal)
42 , lastFlushedSeqID_(0)
43 , highestSeqIDSeen_(0)
44 , enq_timeout_(pset.get<double>(
"event_queue_wait_time", 5.0))
45 , enq_check_count_(pset.get<size_t>(
"event_queue_check_count", 5000))
46 , printSummaryStats_(pset.get<bool>(
"print_event_store_stats", false))
47 , incomplete_event_report_interval_ms_(pset.get<int>(
"incomplete_event_report_interval_ms", -1))
48 , last_incomplete_event_report_time_(std::chrono::steady_clock::now())
50 , art_thread_wait_ms_(pset.get<int>(
"art_thread_wait_ms", 4000))
52 TLOG_DEBUG(
"EventStore") <<
"EventStore CONSTRUCTOR" << TLOG_ENDL;
54 setup_requests_(pset.get<std::string>(
"request_address",
"227.128.12.26"));
56 auto rmConfig = pset.get<fhicl::ParameterSet>(
"routing_token_config", fhicl::ParameterSet());
57 send_routing_tokens_ = rmConfig.get<
bool>(
"use_routing_master",
false);
58 token_port_ = rmConfig.get<
int>(
"routing_token_port", 35555);
59 token_address_ = rmConfig.get<std::string>(
"routing_master_hostname",
"localhost");
61 TRACE(12,
"artdaq::EventStore::EventStore ctor - reader_thread_ initialized");
65 size_t num_fragments_per_event,
69 ART_CMDLINE_FCN* reader)
70 :
EventStore(pset, num_fragments_per_event, run, 50, 50)
72 reader_thread_ = (std::async(std::launch::async, reader, argc, argv));
76 size_t num_fragments_per_event,
78 const std::string& configString,
79 ART_CFGSTRING_FCN* reader)
80 :
EventStore(pset, num_fragments_per_event, run, 20, 20)
82 reader_thread_ = (std::async(std::launch::async, reader, configString));
87 TLOG_DEBUG(
"EventStore") <<
"Shutting down EventStore" << TLOG_ENDL;
88 if (printSummaryStats_)
92 shutdown(request_socket_, 2);
93 close(request_socket_);
94 shutdown(token_socket_, 2);
99 bool printWarningWhenFragmentIsDropped)
103 assert(pfrag !=
nullptr);
104 assert(pfrag->fragmentID() != Fragment::InvalidFragmentID);
114 if (pfrag->sequenceID() > highestSeqIDSeen_)
116 highestSeqIDSeen_ = pfrag->sequenceID();
119 Fragment::timestamp_t timestamp = pfrag->timestamp();
124 std::lock_guard<std::mutex> lk(request_mutex_);
125 active_requests_[highestSeqIDSeen_] = timestamp;
132 if (send_requests_ && request_mode_ == detail::RequestMessageMode::EndOfRun)
134 std::lock_guard<std::mutex> lk(request_mutex_);
137 Fragment::sequence_id_t sequence_id = ((pfrag->sequenceID() - (1 + lastFlushedSeqID_)) / seqIDModulus_) + 1;
138 TRACE(13,
"EventStore::insert seq=%lu fragID=%d id=%d lastFlushed=%lu seqIDMod=%d seq=%lu"
139 , pfrag->sequenceID(), pfrag->fragmentID(), my_rank, lastFlushedSeqID_, seqIDModulus_, sequence_id);
144 EventMap::iterator loc = events_.lower_bound(sequence_id);
146 if (loc == events_.end() || events_.key_comp()(sequence_id, loc->first))
150 RawEvent_ptr newevent(
new RawEvent(run_id_, subrun_id_, pfrag->sequenceID()));
152 events_.insert(loc, EventMap::value_type(sequence_id, newevent));
156 loc->second->insertFragment(std::move(pfrag));
157 if (loc->second->numFragments() == num_fragments_per_event_)
162 RawEvent_ptr complete_event(loc->second);
163 complete_event->markComplete();
169 std::lock_guard<std::mutex> lk(request_mutex_);
170 active_requests_.erase(sequence_id);
175 MonitoredQuantityPtr mqPtr = StatisticsCollection::getInstance().
177 if (mqPtr.get() != 0)
179 mqPtr->addSample(complete_event->wordCount());
181 TRACE(14,
"EventStore::insert seq=%lu enqTimedWait start", sequence_id);
182 bool enqSuccess = queue_.enqTimedWait(complete_event, enq_timeout_);
183 TRACE(enqSuccess ? 14 : 0,
"EventStore::insert seq=%lu enqTimedWait complete", sequence_id);
186 metricMan->sendMetric(
"Current Event Number", sequence_id,
"id", 2, MetricMode::LastPoint);
191 if (printWarningWhenFragmentIsDropped)
193 TLOG_WARNING(
"EventStore") <<
"Enqueueing event " << sequence_id
194 <<
" FAILED, queue size = "
196 "; apparently no events were removed from this process's queue during the " << std::to_string(enq_timeout_.count())
197 <<
"-second timeout period" << TLOG_ENDL;
201 TLOG_DEBUG(
"EventStore") <<
"Enqueueing event " << sequence_id
202 <<
" FAILED, queue size = "
204 "; apparently no events were removed from this process's queue during the " << std::to_string(enq_timeout_.count())
205 <<
"-second timeout period" << TLOG_ENDL;
210 send_routing_token_(1);
213 MonitoredQuantityPtr mqPtr = StatisticsCollection::getInstance().
215 if (mqPtr.get() != 0)
217 mqPtr->addSample(events_.size());
227 TRACE(12,
"EventStore: Testing if queue is full");
230 size_t sleepTime = 1000000 * (enq_timeout_.count() / enq_check_count_);
231 TRACE(12,
"EventStore: sleepTime is %lu.", sleepTime);
232 size_t loopCount = 0;
233 while (loopCount < enq_check_count_ && queue_.full())
240 rejectedFragment = std::move(pfrag);
244 TRACE(12,
"EventStore: Testing if there's room in the EventStore");
245 auto incomplete_full = events_.size() >= max_incomplete_count_;
248 EventMap::iterator loc = events_.lower_bound(pfrag->sequenceID());
250 if (loc == events_.end() || events_.key_comp()(pfrag->sequenceID(), loc->first))
252 rejectedFragment = std::move(pfrag);
257 TRACE(12,
"EventStore: Performing insert");
265 TLOG_DEBUG(
"EventStore") <<
"EventStore::endOfData" << TLOG_ENDL;
266 RawEvent_ptr end_of_data(
nullptr);
267 TRACE(4,
"EventStore::endOfData: Enqueuing end_of_data event");
268 bool enqSuccess = queue_.enqTimedWait(end_of_data, enq_timeout_);
273 TRACE(4,
"EventStore::endOfData: Getting return code from art thread");
274 readerReturnValue = reader_thread_.get();
280 seqIDModulus_ = seqIDModulus;
286 size_t initialStoreSize = events_.size();
287 TLOG_DEBUG(
"EventStore") <<
"Flushing " << initialStoreSize
288 <<
" stale events from the EventStore." << TLOG_ENDL;
289 EventMap::iterator loc;
290 std::vector<sequence_id_t> flushList;
291 for (loc = events_.begin(); loc != events_.end(); ++loc)
293 RawEvent_ptr complete_event(loc->second);
294 MonitoredQuantityPtr mqPtr = StatisticsCollection::getInstance().
296 if (mqPtr.get() != 0)
298 mqPtr->addSample(complete_event->wordCount());
300 enqSuccess = queue_.enqTimedWait(complete_event, enq_timeout_);
307 flushList.push_back(loc->first);
310 for (
size_t idx = 0; idx < flushList.size(); ++idx)
312 events_.erase(flushList[idx]);
314 TLOG_DEBUG(
"EventStore") <<
"Done flushing " << flushList.size()
315 <<
" stale events from the EventStore." << TLOG_ENDL;
317 lastFlushedSeqID_ = highestSeqIDSeen_;
318 return (flushList.size() >= initialStoreSize);
323 if (!queue_.queueReaderIsReady())
325 TLOG_WARNING(
"EventStore") <<
"Run start requested, but the art thread is not yet ready, waiting up to " << art_thread_wait_ms_ <<
" msec..." << TLOG_ENDL;
326 while (!queue_.queueReaderIsReady() && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - reader_thread_launch_time_).count() < art_thread_wait_ms_)
330 if (queue_.queueReaderIsReady())
332 auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(queue_.getReadyTime() - reader_thread_launch_time_).count();
333 TLOG_INFO(
"EventStore") <<
"art initialization took (roughly) " << std::setw(4) << std::to_string(dur) <<
" ms." << TLOG_ENDL;
337 auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - reader_thread_launch_time_).count();
338 TLOG_ERROR(
"EventStore") <<
"art thread still not ready after " << dur <<
" ms. Continuing to start..." << TLOG_ENDL;
343 lastFlushedSeqID_ = 0;
344 highestSeqIDSeen_ = 0;
345 send_routing_token_(max_queue_size_);
346 TLOG_DEBUG(
"EventStore") <<
"Starting run " << run_id_
347 <<
", max queue size = "
349 <<
", queue capacity = "
352 << queue_.size() << TLOG_ENDL;
355 double runSubrun = run_id_ + ((double)subrun_id_ / 10000);
356 metricMan->sendMetric(
"Run Number", runSubrun,
"Run:Subrun", 1, MetricMode::LastPoint);
365 double runSubrun = run_id_ + ((double)subrun_id_ / 10000);
366 metricMan->sendMetric(
"Run Number", runSubrun,
"Run:Subrun", 1, MetricMode::LastPoint);
372 RawEvent_ptr endOfRunEvent(
new RawEvent(run_id_, subrun_id_, 0));
373 std::unique_ptr<artdaq::Fragment>
375 Fragment(static_cast<size_t>
376 (ceil(
sizeof(my_rank) /
377 static_cast<double>(
sizeof(Fragment::value_type))))));
379 endOfRunFrag->setSystemType(Fragment::EndOfRunFragmentType);
380 *endOfRunFrag->dataBegin() = my_rank;
381 endOfRunEvent->insertFragment(std::move(endOfRunFrag));
383 return queue_.enqTimedWait(endOfRunEvent, enq_timeout_);
388 RawEvent_ptr endOfSubrunEvent(
new RawEvent(run_id_, subrun_id_, 0));
389 std::unique_ptr<artdaq::Fragment>
391 Fragment(static_cast<size_t>
392 (ceil(
sizeof(my_rank) /
393 static_cast<double>(
sizeof(Fragment::value_type))))));
395 endOfSubrunFrag->setSystemType(Fragment::EndOfSubrunFragmentType);
396 *endOfSubrunFrag->dataBegin() = my_rank;
397 endOfSubrunEvent->insertFragment(std::move(endOfSubrunFrag));
399 return queue_.enqTimedWait(endOfSubrunEvent, enq_timeout_);
404 request_mode_ = mode;
405 if (send_requests_ && request_mode_ == detail::RequestMessageMode::EndOfRun)
412 EventStore::initStatistics_()
414 MonitoredQuantityPtr mqPtr = StatisticsCollection::getInstance().
416 if (mqPtr.get() == 0)
418 mqPtr.reset(
new MonitoredQuantity(3.0, 300.0));
419 StatisticsCollection::getInstance().
424 mqPtr = StatisticsCollection::getInstance().
426 if (mqPtr.get() == 0)
428 mqPtr.reset(
new MonitoredQuantity(3.0, 300.0));
429 StatisticsCollection::getInstance().
436 EventStore::reportStatistics_()
438 MonitoredQuantityPtr mqPtr = StatisticsCollection::getInstance().
440 if (mqPtr.get() != 0)
444 <<
"_" << setfill(
'0') << setw(4) << my_rank <<
".txt";
445 std::string filename = oss.str();
446 ofstream outStream(filename.c_str());
447 mqPtr->waitUntilAccumulatorsHaveBeenFlushed(3.0);
448 artdaq::MonitoredQuantityStats stats;
449 mqPtr->getStats(stats);
450 outStream <<
"EventStore rank " << my_rank <<
": events processed = "
451 << stats.fullSampleCount <<
" at " << stats.fullSampleRate
452 <<
" events/sec, data rate = "
453 << (stats.fullValueRate *
sizeof(RawDataType)
454 / 1024.0 / 1024.0) <<
" MB/sec, duration = "
455 << stats.fullDuration <<
" sec" << std::endl
456 <<
" minimum event size = "
457 << (stats.fullValueMin *
sizeof(RawDataType)
459 <<
" MB, maximum event size = "
460 << (stats.fullValueMax *
sizeof(RawDataType)
462 <<
" MB" << std::endl;
463 bool foundTheStart =
false;
464 for (
int idx = 0; idx < (int)stats.recentBinnedDurations.size(); ++idx)
466 if (stats.recentBinnedDurations[idx] > 0.0)
468 foundTheStart =
true;
472 outStream <<
" " << std::fixed << std::setprecision(3)
473 << stats.recentBinnedEndTimes[idx]
474 <<
": " << stats.recentBinnedSampleCounts[idx]
476 << (stats.recentBinnedSampleCounts[idx] /
477 stats.recentBinnedDurations[idx])
478 <<
" events/sec, data rate = "
479 << (stats.recentBinnedValueSums[idx] *
480 sizeof(RawDataType) / 1024.0 / 1024.0 /
481 stats.recentBinnedDurations[idx])
482 <<
" MB/sec, bin size = "
483 << stats.recentBinnedDurations[idx]
484 <<
" sec" << std::endl;
490 mqPtr = StatisticsCollection::getInstance().
492 if (mqPtr.get() != 0)
496 << setw(4) << run_id_
497 <<
"_" << setfill(
'0') << setw(4) << my_rank <<
".txt";
498 std::string filename = oss.str();
499 ofstream outStream(filename.c_str());
500 mqPtr->waitUntilAccumulatorsHaveBeenFlushed(3.0);
501 artdaq::MonitoredQuantityStats stats;
502 mqPtr->getStats(stats);
503 outStream <<
"EventStore rank " << my_rank <<
": fragments processed = "
504 << stats.fullSampleCount <<
" at " << stats.fullSampleRate
505 <<
" fragments/sec, average incomplete event count = "
506 << stats.fullValueAverage <<
" duration = "
507 << stats.fullDuration <<
" sec" << std::endl
508 <<
" minimum incomplete event count = "
509 << stats.fullValueMin <<
", maximum incomplete event count = "
510 << stats.fullValueMax << std::endl;
511 bool foundTheStart =
false;
512 for (
int idx = 0; idx < (int)stats.recentBinnedDurations.size(); ++idx)
514 if (stats.recentBinnedDurations[idx] > 0.0)
516 foundTheStart =
true;
518 if (foundTheStart && stats.recentBinnedSampleCounts[idx] > 0.0)
520 outStream <<
" " << std::fixed << std::setprecision(3)
521 << stats.recentBinnedEndTimes[idx]
522 <<
": " << stats.recentBinnedSampleCounts[idx]
524 << (stats.recentBinnedSampleCounts[idx] /
525 stats.recentBinnedDurations[idx])
526 <<
" fragments/sec, average incomplete event count = "
527 << (stats.recentBinnedValueSums[idx] /
528 stats.recentBinnedSampleCounts[idx])
530 << stats.recentBinnedDurations[idx]
531 <<
" sec" << std::endl;
534 outStream <<
"Incomplete count now = " << events_.size() << std::endl;
540 EventStore::setup_requests_(std::string request_address)
544 request_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
545 if (!request_socket_)
547 TLOG_ERROR(
"EventStore") <<
"I failed to create the socket for sending Data Requests!" << TLOG_ENDL;
550 int sts =
ResolveHost(request_address.c_str(), request_port_, request_addr_);
553 TLOG_ERROR(
"EventStore") <<
"Unable to resolve Data Request address" << TLOG_ENDL;
557 if (multicast_out_addr_ !=
"localhost")
560 int sts =
ResolveHost(multicast_out_addr_.c_str(), addr);
563 TLOG_ERROR(
"EventStore") <<
"Unable to resolve multicast interface address" << TLOG_ENDL;
568 if (setsockopt(request_socket_, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(yes)) < 0)
570 TLOG_ERROR(
"EventStore") <<
"Unable to enable port reuse on request socket" << TLOG_ENDL;
573 if (setsockopt(request_socket_, IPPROTO_IP, IP_MULTICAST_IF, &addr,
sizeof(addr)) == -1)
575 TLOG_ERROR(
"EventStore") <<
"Cannot set outgoing interface." << TLOG_ENDL;
580 if (setsockopt(request_socket_, SOL_SOCKET, SO_BROADCAST, (
void*)&yes,
sizeof(
int)) == -1)
582 TLOG_ERROR(
"EventStore") <<
"Cannot set request socket to broadcast." << TLOG_ENDL;
589 EventStore::setup_tokens_()
591 if (send_routing_tokens_)
593 TLOG_DEBUG(
"EventStore") <<
"Creating Routing Token sending socket" << TLOG_ENDL;
594 token_socket_ =
TCPConnect(token_address_.c_str(), token_port_);
597 TLOG_ERROR(
"EventStore") <<
"I failed to create the socket for sending Routing Tokens!" << TLOG_ENDL;
603 void EventStore::do_send_request_()
605 std::this_thread::sleep_for(std::chrono::microseconds(request_delay_));
607 detail::RequestMessage message;
609 std::lock_guard<std::mutex> lk(request_mutex_);
610 for (
auto& req : active_requests_)
612 message.addRequest(req.first, req.second);
615 message.header()->mode = request_mode_;
616 char str[INET_ADDRSTRLEN];
617 inet_ntop(AF_INET, &(request_addr_.sin_addr), str, INET_ADDRSTRLEN);
618 TLOG_DEBUG(
"EventStore") <<
"Sending request for " << std::to_string(message.size()) <<
" events to multicast group " << str << TLOG_ENDL;
619 if (sendto(request_socket_, message.header(),
sizeof(detail::RequestHeader), 0, (
struct sockaddr *)&request_addr_,
sizeof(request_addr_)) < 0)
621 TLOG_ERROR(
"EventStore") <<
"Error sending request message header" << TLOG_ENDL;
623 if (sendto(request_socket_, message.buffer(),
sizeof(detail::RequestPacket) * message.size(), 0, (
struct sockaddr *)&request_addr_,
sizeof(request_addr_)) < 0)
625 TLOG_ERROR(
"EventStore") <<
"Error sending request message data" << TLOG_ENDL;
629 void EventStore::send_routing_token_(
int nSlots)
631 TLOG_DEBUG(
"EventStore") <<
"send_routing_token_ called, send_routing_tokens_=" << std::boolalpha << send_routing_tokens_ << TLOG_ENDL;
632 if (!send_routing_tokens_)
return;
633 if (token_socket_ == -1) setup_tokens_();
634 detail::RoutingToken token;
635 token.header = TOKEN_MAGIC;
636 token.rank = my_rank;
637 token.new_slots_free = nSlots;
639 TLOG_DEBUG(
"EventStore") <<
"Sending RoutingToken to " << token_address_ <<
":" << token_port_ << TLOG_ENDL;
641 while (sts <
sizeof(detail::RoutingToken))
643 auto res = send(token_socket_, reinterpret_cast<uint8_t*>(&token) + sts,
sizeof(detail::RoutingToken) - sts, 0);
651 TLOG_DEBUG(
"EventStore") <<
"Done sending RoutingToken to " << token_address_ <<
":" << token_port_ << TLOG_ENDL;
655 EventStore::send_request_()
657 std::thread request([=] { do_send_request_(); });
666 metricMan->sendMetric(
"Incomplete Event Count", events_.size(),
"events", 1, MetricMode::LastPoint);
668 MonitoredQuantityPtr mqPtr = StatisticsCollection::getInstance().
670 if (mqPtr.get() != 0)
672 artdaq::MonitoredQuantityStats stats;
673 mqPtr->getStats(stats);
675 metricMan->sendMetric(
"Event Count", static_cast<unsigned long>(stats.fullSampleCount),
"events", 1, MetricMode::Accumulate);
676 metricMan->sendMetric(
"Event Rate", stats.recentSampleRate,
"events/sec", 1, MetricMode::Average);
677 metricMan->sendMetric(
"Average Event Size", (stats.recentValueAverage *
sizeof(artdaq::RawDataType)),
"bytes/fragment", 2, MetricMode::Average);
678 metricMan->sendMetric(
"Data Rate", (stats.recentValueRate *
sizeof(artdaq::RawDataType)),
"bytes/sec", 2, MetricMode::Average);
681 if (incomplete_event_report_interval_ms_ > 0 && events_.size())
683 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - last_incomplete_event_report_time_).count() < incomplete_event_report_interval_ms_)
return;
684 last_incomplete_event_report_time_ = std::chrono::steady_clock::now();
685 std::ostringstream oss;
686 oss <<
"Incomplete Events (" << num_fragments_per_event_ <<
"): ";
687 for (
auto& ev : events_)
689 oss << ev.first <<
" (" << ev.second->numFragments() <<
"), ";
691 TLOG_DEBUG(
"EventStore") << oss.str() << TLOG_ENDL;
void insert(FragmentPtr pfrag, bool printWarningWhenFragmentIsDropped=true)
Give ownership of the Fragment to the EventStore.
static const std::string EVENT_RATE_STAT_KEY
Key for the Event Rate MonitoredQuantity.
void setRequestMode(detail::RequestMessageMode mode)
Set the mode for RequestMessages. Used to indicate when EventStore should enter "EndOfRun" mode...
int ResolveHost(char const *host_in, in_addr &addr)
Convert a string hostname to a in_addr suitable for socket communication.
int TCPConnect(char const *host_in, int dflt_port, long flags=0, int sndbufsiz=0)
Connect to a host on a given port.
bool endOfData(int &readerReturnValue)
Indicate that the end of input has been reached to the art thread.
EventStoreInsertResult
This enumeration contains possible status codes of insertion attempts.
void startRun(run_id_t runID)
Start a Run.
EventStore()=delete
Default Constructor is deleted.
virtual ~EventStore()
EventStore Destructor.
The Fragment was successfully inserted.
void startSubrun()
Start a new Subrun, incrementing the subrun number.
The EventStore class collects Fragment objects, until it receives a complete event, at which point the event is handed over to the art thread.
static const std::string INCOMPLETE_EVENT_STAT_KEY
Key for the Incomplete Events MonitoredQuantity.
The EventStore is full, but the Fragment was accepted as it is for an already-open event...
void setSeqIDModulus(unsigned int seqIDModulus)
Set the parameter that will be used to determine which sequence IDs get grouped together into events...
void sendMetrics()
Send metrics to the MetricManager, if one has been instantiated in the application.
bool flushData()
Push any incomplete events onto the queue.
RawEvent::run_id_t run_id_t
Copy RawEvent::run_id_t into local scope.
The EventStore is full, and the Fragment was rejected.
The Fragment was rejected, because the RawEventQueue is full.
bool endRun()
Send an EndOfRunFragment to the art thread.
bool endSubrun()
Send an EndOfSubRunFragment to the art thread.