Coverage Report

Created: 2026-08-14 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/zeek/src/threading/MsgThread.cc
Line
Count
Source
1
// See the file "COPYING" in the main distribution directory for copyright.
2
3
#include "zeek/threading/MsgThread.h"
4
5
#include <fcntl.h>
6
#include <unistd.h>
7
#include <csignal>
8
9
#include "zeek/DebugLogger.h"
10
#include "zeek/Desc.h"
11
#include "zeek/Obj.h"
12
#include "zeek/RunState.h"
13
#include "zeek/iosource/Manager.h"
14
#include "zeek/threading/Manager.h"
15
16
// Set by Zeek's main signal handler.
17
extern int signal_val;
18
19
namespace zeek::threading {
20
namespace detail {
21
22
////// Messages.
23
24
// Signals child thread to shutdown operation.
25
class FinishMessage final : public InputMessage<MsgThread> {
26
public:
27
    FinishMessage(MsgThread* thread, double network_time)
28
0
        : InputMessage<MsgThread>("Finish", thread), network_time(network_time) {}
29
30
0
    bool Process() override {
31
0
        if ( Object()->child_finished )
32
0
            return true;
33
0
        bool result = Object()->OnFinish(network_time);
34
0
        Object()->Finished();
35
0
        return result;
36
0
    }
37
38
private:
39
    double network_time;
40
};
41
42
// Signals main thread that operations shut down.
43
class FinishedMessage final : public OutputMessage<MsgThread> {
44
public:
45
0
    FinishedMessage(MsgThread* thread) : OutputMessage<MsgThread>("FinishedMessage", thread) {}
46
47
0
    bool Process() override {
48
0
        Object()->main_finished = true;
49
0
        return true;
50
0
    }
51
};
52
53
/// Sends a heartbeat to the child thread.
54
class HeartbeatMessage final : public InputMessage<MsgThread> {
55
public:
56
    HeartbeatMessage(MsgThread* thread, double arg_network_time, double arg_current_time)
57
0
        : InputMessage<MsgThread>("Heartbeat", thread) {
58
0
        network_time = arg_network_time;
59
0
        current_time = arg_current_time;
60
0
    }
61
62
0
    bool Process() override { return Object()->OnHeartbeat(network_time, current_time); }
63
64
private:
65
    double network_time;
66
    double current_time;
67
};
68
69
// A message from the child to be passed on to the Reporter.
70
class ReporterMessage final : public OutputMessage<MsgThread> {
71
public:
72
    enum Type : uint8_t { INFO, WARNING, ERROR, FATAL_ERROR, FATAL_ERROR_WITH_CORE, INTERNAL_WARNING, INTERNAL_ERROR };
73
74
    ReporterMessage(Type arg_type, MsgThread* thread, std::string_view arg_msg)
75
0
        : OutputMessage<MsgThread>("ReporterMessage", thread) {
76
0
        type = arg_type;
77
0
        msg = util::copy_string(arg_msg.data(), arg_msg.size());
78
0
    }
79
80
0
    ~ReporterMessage() override { delete[] msg; }
81
82
    bool Process() override;
83
84
private:
85
    const char* msg;
86
    Type type;
87
};
88
89
// A message from the child to the main process, requesting suicide.
90
class KillMeMessage final : public OutputMessage<MsgThread> {
91
public:
92
0
    KillMeMessage(MsgThread* thread) : OutputMessage<MsgThread>("ReporterMessage", thread) {}
93
94
0
    bool Process() override {
95
0
        Object()->SignalStop();
96
0
        Object()->WaitForStop();
97
0
        thread_mgr->KillThread(Object());
98
0
        return true;
99
0
    }
100
};
101
102
// A debug message from the child to be passed on to the DebugLogger.
103
class DebugMessage final : public OutputMessage<MsgThread> {
104
public:
105
    DebugMessage(DebugStream arg_stream, MsgThread* thread, std::string_view arg_msg)
106
850k
        : OutputMessage<MsgThread>("DebugMessage", thread) {
107
850k
        stream = arg_stream;
108
850k
        msg = util::copy_string(arg_msg.data(), arg_msg.size());
109
850k
    }
110
111
0
    ~DebugMessage() override { delete[] msg; }
112
113
0
    bool Process() override {
114
0
        zeek::detail::debug_logger.Log(stream, "%s: %s", Object()->Name(), msg);
115
0
        return true;
116
0
    }
117
118
private:
119
    const char* msg;
120
    DebugStream stream;
121
};
122
123
// An event that the child wants to pass into the main event queue
124
class SendEventMessage final : public OutputMessage<MsgThread> {
125
public:
126
    SendEventMessage(MsgThread* thread, const char* name, const int num_vals, Value** val)
127
0
        : OutputMessage<MsgThread>("SendEvent", thread), name(util::copy_string(name)), num_vals(num_vals), val(val) {}
128
129
0
    ~SendEventMessage() override { delete[] name; }
130
131
0
    bool Process() override {
132
0
        bool success = thread_mgr->SendEvent(Object(), name, num_vals, val);
133
134
0
        if ( ! success )
135
0
            reporter->Error("SendEvent for event %s failed", name);
136
137
0
        return true; // We do not want to die if sendEvent fails because the event did not return.
138
0
    }
139
140
private:
141
    const char* name;
142
    const int num_vals;
143
    Value** val;
144
};
145
146
0
bool ReporterMessage::Process() {
147
0
    switch ( type ) {
148
0
        case INFO: reporter->Info("%s: %s", Object()->Name(), msg); break;
149
150
0
        case WARNING: reporter->Warning("%s: %s", Object()->Name(), msg); break;
151
152
0
        case ERROR: reporter->Error("%s: %s", Object()->Name(), msg); break;
153
154
0
        case FATAL_ERROR: reporter->FatalError("%s: %s", Object()->Name(), msg); break;
155
156
0
        case FATAL_ERROR_WITH_CORE: reporter->FatalErrorWithCore("%s: %s", Object()->Name(), msg); break;
157
158
0
        case INTERNAL_WARNING: reporter->InternalWarning("%s: %s", Object()->Name(), msg); break;
159
160
0
        case INTERNAL_ERROR: reporter->InternalError("%s: %s", Object()->Name(), msg); break;
161
162
0
        default: reporter->InternalError("unknown ReporterMessage type %d", type);
163
0
    }
164
165
0
    return true;
166
0
}
167
168
// This is the IO source used by MsgThread.
169
//
170
// The lifetime of the IO source is decoupled from
171
// the thread. The thread may be terminated prior
172
// to the IO source being properly unregistered and
173
// removed by the IO manager.
174
class IOSource : public iosource::IOSource {
175
public:
176
339
    explicit IOSource(MsgThread* thread) : thread(thread) {
177
339
        if ( ! iosource_mgr->RegisterFd(flare.FD(), this) )
178
0
            reporter->InternalError("Failed to register MsgThread FD with iosource_mgr");
179
180
339
        SetClosed(false);
181
339
    }
182
183
0
    ~IOSource() override {
184
0
        if ( ! iosource_mgr->UnregisterFd(flare.FD(), this) )
185
0
            reporter->InternalError("Failed to unregister MsgThread FD from iosource_mgr");
186
0
    }
187
188
0
    void Process() override {
189
0
        flare.Extinguish();
190
191
0
        if ( thread )
192
0
            thread->Process();
193
0
    }
194
195
0
    const char* Tag() override { return thread ? thread->Name() : "<MsgThread orphan>"; }
196
197
0
    double GetNextTimeout() override { return -1; }
198
199
200
851k
    void Fire() { flare.Fire(); };
201
202
0
    void Close() {
203
0
        thread = nullptr;
204
0
        SetClosed(true);
205
0
    }
206
207
private:
208
    MsgThread* thread = nullptr;
209
    zeek::detail::Flare flare;
210
};
211
212
} // namespace detail
213
214
////// Methods.
215
216
851k
Message::~Message() { delete[] name; }
217
218
339
MsgThread::MsgThread() : BasicThread(), queue_in(this, nullptr), queue_out(nullptr, this) {
219
339
    cnt_sent_in.store(0);
220
339
    cnt_sent_out.store(0);
221
222
339
    main_finished = false;
223
339
    child_finished = false;
224
339
    child_sent_finish = false;
225
339
    failed = false;
226
339
    thread_mgr->AddMsgThread(this);
227
228
339
    io_source = new detail::IOSource(this);
229
230
    // Register IOSource as non-counting lifetime managed IO source.
231
339
    iosource_mgr->Register(io_source, true);
232
339
}
233
234
0
MsgThread::~MsgThread() {
235
    // Unregister this thread from the IO source so we don't
236
    // get Process() callbacks anymore. The IO source itself
237
    // is life-time managed by the IO manager.
238
0
    if ( io_source ) {
239
0
        io_source->Close();
240
0
        io_source = nullptr;
241
0
    }
242
0
}
243
244
0
void MsgThread::OnSignalStop() {
245
0
    if ( main_finished || Killed() || child_sent_finish )
246
0
        return;
247
248
0
    child_sent_finish = true;
249
    // Signal thread to terminate.
250
0
    SendIn(new detail::FinishMessage(this, run_state::network_time), true);
251
0
}
252
253
0
void MsgThread::OnWaitForStop() {
254
0
    int signal_count = 0;
255
0
    int old_signal_val = signal_val;
256
0
    signal_val = 0;
257
258
0
    int cnt = 0;
259
0
    uint64_t last_size = 0;
260
0
    uint64_t cur_size = 0;
261
262
0
    while ( ! main_finished ) {
263
        // Terminate if we get another kill signal.
264
0
        if ( signal_val == SIGTERM || signal_val == SIGINT ) {
265
0
            ++signal_count;
266
267
0
            if ( signal_count == 1 ) {
268
                // Abort all threads here so that we won't hang next
269
                // on another one.
270
0
                fprintf(stderr, "received signal while waiting for thread %s, aborting all ...\n", Name());
271
0
                thread_mgr->KillThreads();
272
0
            }
273
0
            else {
274
                // More than one signal. Abort processing
275
                // right away. on another one.
276
0
                fprintf(stderr, "received another signal while waiting for thread %s, aborting processing\n", Name());
277
0
                exit(1);
278
0
            }
279
280
0
            signal_val = 0;
281
0
        }
282
283
0
        if ( ! Killed() )
284
0
            queue_in.WakeUp();
285
286
0
        while ( HasOut() ) {
287
0
            Message* msg = RetrieveOut();
288
0
            assert(msg);
289
290
0
            if ( ! msg->Process() )
291
0
                reporter->Error("%s failed during thread termination", msg->Name());
292
293
0
            delete msg;
294
0
        }
295
296
0
        if ( ! Killed() )
297
0
            usleep(1000);
298
0
    }
299
300
0
    signal_val = old_signal_val;
301
0
}
302
303
0
void MsgThread::OnKill() {
304
    // Ensure the IO source is closed and won't call Process() on this
305
    // thread anymore. The thread got killed, so the threading manager will
306
    // remove it forcefully soon.
307
0
    if ( io_source ) {
308
0
        io_source->Close();
309
0
        io_source = nullptr;
310
0
    }
311
312
    // Send a message to unblock the reader if its currently waiting for
313
    // input. This is just an optimization to make it terminate more
314
    // quickly, even without the message it will eventually time out.
315
0
    queue_in.WakeUp();
316
0
}
317
318
0
void MsgThread::Heartbeat() {
319
0
    if ( child_sent_finish )
320
0
        return;
321
322
0
    SendIn(new detail::HeartbeatMessage(this, run_state::network_time, util::current_time()));
323
0
}
324
325
0
void MsgThread::Finished() {
326
0
    child_finished = true;
327
0
    SendOut(new detail::FinishedMessage(this));
328
0
}
329
330
850k
std::string MsgThread::BuildMsgWithLocation(const char* msg) {
331
850k
    ODesc desc;
332
333
850k
    if ( auto* location = GetLocationInfo() ) {
334
0
        location->Describe(&desc);
335
0
        desc.Add(": ");
336
0
    }
337
338
850k
    desc.Add(msg);
339
850k
    return desc.Description();
340
850k
}
341
342
0
void MsgThread::Info(const char* msg) {
343
0
    SendOut(new detail::ReporterMessage(detail::ReporterMessage::INFO, this, BuildMsgWithLocation(msg)));
344
0
}
345
346
0
void MsgThread::Warning(const char* msg) {
347
0
    SendOut(new detail::ReporterMessage(detail::ReporterMessage::WARNING, this, BuildMsgWithLocation(msg)));
348
0
}
349
350
0
void MsgThread::Error(const char* msg) {
351
0
    SendOut(new detail::ReporterMessage(detail::ReporterMessage::ERROR, this, BuildMsgWithLocation(msg)));
352
0
}
353
354
0
void MsgThread::FatalError(const char* msg) {
355
0
    SendOut(new detail::ReporterMessage(detail::ReporterMessage::FATAL_ERROR, this, BuildMsgWithLocation(msg)));
356
0
}
357
358
0
void MsgThread::FatalErrorWithCore(const char* msg) {
359
0
    SendOut(
360
0
        new detail::ReporterMessage(detail::ReporterMessage::FATAL_ERROR_WITH_CORE, this, BuildMsgWithLocation(msg)));
361
0
}
362
363
0
void MsgThread::InternalWarning(const char* msg) {
364
0
    SendOut(new detail::ReporterMessage(detail::ReporterMessage::INTERNAL_WARNING, this, BuildMsgWithLocation(msg)));
365
0
}
366
367
0
void MsgThread::InternalError(const char* msg) {
368
0
    fprintf(stderr, "internal error in thread: %s\n", BuildMsgWithLocation(msg).c_str());
369
0
    abort();
370
0
}
371
372
850k
void MsgThread::Debug(DebugStream stream, const char* msg) {
373
850k
    SendOut(new detail::DebugMessage(stream, this, BuildMsgWithLocation(msg)));
374
850k
}
375
376
851k
void MsgThread::SendIn(BasicInputMessage* msg, bool force) {
377
851k
    if ( Terminating() && ! force ) {
378
0
        delete msg;
379
0
        return;
380
0
    }
381
382
851k
    DBG_LOG(DBG_THREADING, "Sending '%s' to %s ...", msg->Name(), Name());
383
384
851k
    queue_in.Put(msg);
385
851k
    ++cnt_sent_in;
386
387
851k
    zeek::thread_mgr->MessageIn();
388
851k
}
389
390
850k
void MsgThread::SendOut(BasicOutputMessage* msg, bool force) {
391
850k
    if ( Terminating() && ! force ) {
392
0
        delete msg;
393
0
        return;
394
0
    }
395
396
850k
    queue_out.Put(msg);
397
398
850k
    ++cnt_sent_out;
399
400
850k
    zeek::thread_mgr->MessageOut();
401
402
850k
    if ( io_source )
403
851k
        io_source->Fire();
404
850k
}
405
406
0
void MsgThread::SendEvent(const char* name, const int num_vals, Value** vals) {
407
0
    SendOut(new detail::SendEventMessage(this, name, num_vals, vals));
408
0
}
409
410
0
BasicOutputMessage* MsgThread::RetrieveOut() {
411
0
    BasicOutputMessage* msg = queue_out.Get();
412
0
    if ( ! msg )
413
0
        return nullptr;
414
415
0
    DBG_LOG(DBG_THREADING, "Retrieved '%s' from %s", msg->Name(), Name());
416
417
0
    return msg;
418
0
}
419
420
855k
BasicInputMessage* MsgThread::RetrieveIn() {
421
855k
    BasicInputMessage* msg = queue_in.Get();
422
423
855k
    if ( ! msg )
424
3.93k
        return nullptr;
425
426
851k
#ifdef DEBUG
427
851k
    std::string s = Fmt("Retrieved '%s' in %s", msg->Name(), Name());
428
851k
    Debug(DBG_THREADING, s.c_str());
429
851k
#endif
430
431
851k
    return msg;
432
855k
}
433
434
339
void MsgThread::Run() {
435
855k
    while ( ! (child_finished || Killed()) ) {
436
855k
        BasicInputMessage* msg = RetrieveIn();
437
438
855k
        if ( ! msg )
439
3.93k
            continue;
440
441
851k
        bool result = msg->Process();
442
443
851k
        delete msg;
444
445
851k
        if ( ! result ) {
446
0
            Error("terminating thread");
447
448
            // This will eventually kill this thread, but only
449
            // after all other outgoing messages (in particular
450
            // error messages have been processed by then main
451
            // thread).
452
0
            SendOut(new detail::KillMeMessage(this));
453
0
            failed = true;
454
0
        }
455
851k
    }
456
457
    // In case we haven't sent the finish method yet, do it now. Reading
458
    // global network_time here should be fine, it isn't changing
459
    // anymore.
460
339
    if ( ! child_finished && ! Killed() ) {
461
0
        OnFinish(run_state::network_time);
462
0
        Finished();
463
0
    }
464
339
}
465
466
0
void MsgThread::GetStats(Stats* stats) {
467
0
    stats->sent_in = cnt_sent_in.load();
468
0
    stats->sent_out = cnt_sent_out.load();
469
0
    stats->pending_in = queue_in.Size();
470
0
    stats->pending_out = queue_out.Size();
471
0
    queue_in.GetStats(&stats->queue_in_stats);
472
0
    queue_out.GetStats(&stats->queue_out_stats);
473
0
}
474
475
0
void MsgThread::Process() {
476
0
    while ( HasOut() ) {
477
0
        Message* msg = RetrieveOut();
478
0
        assert(msg);
479
480
0
        if ( ! msg->Process() ) {
481
0
            reporter->Error("%s failed, terminating thread", msg->Name());
482
0
            SignalStop();
483
0
        }
484
485
0
        delete msg;
486
0
    }
487
0
}
488
489
} // namespace zeek::threading