error.h 16.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Spell-QTL  Software suite for the QTL analysis of modern datasets.
 * Copyright (C) 2016,2017  Damien Leroux <damien.leroux@inra.fr>, Sylvain Jasson <sylvain.jasson@inra.fr>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

18
19
#ifndef _SPEL_ERROR_H_
#define _SPEL_ERROR_H_
20
21
22

#include <string>
#include <iostream>
23
#include <exception>
24
25
26
#include <set>
#include <utility>
#include <sstream>
27
#include <thread>
Damien Leroux's avatar
Damien Leroux committed
28
#include <mutex>
29
#include <condition_variable>
30
#include <vector>
31
#include <deque>
32
33
extern "C" {
#include <unistd.h>
Damien Leroux's avatar
WIP.    
Damien Leroux committed
34
#include <sys/ioctl.h>
35
#include <signal.h>
36
}
37
38
39

#define _WHITE "\x1b[37;1m"
#define _RED "\x1b[31;1m"
Damien Leroux's avatar
WIP.    
Damien Leroux committed
40
#define _GREEN "\x1b[32;1m"
41
42
43
44
#define _YELLOW "\x1b[33;1m"
#define _CYAN "\x1b[36;1m"
#define _NORMAL "\x1b[0;m"

45
46
47
48
49
50
51
#define _DBG_INDENT_MARK '\x11'
#define _DBG_DEDENT_MARK '\x12'
#define _DBG_INDENT_MARK_S "\x11"
#define _DBG_DEDENT_MARK_S "\x12"
#define _DBG_SYNC_MARK '\x13'
#define _DBG_SYNC_MARK_S "\x13"

52
53
#define MSG_HANDLER_IS_SYNCED

54
55
56
57
58
enum msg_channel { Out, Err, Log };

struct message_struc {
    msg_channel channel;
    std::string message;
59
    message_struc(msg_channel c, std::string&& msg) : channel(c), message(std::move(msg)) {}
60
61
62
63
64
};

typedef std::shared_ptr<message_struc> message_handle;

/*#define MAKE_MESSAGE(_dest_, _expr_) do { std::stringstream __s; __s << _expr_; _dest_ = __s.str(); } while (0)*/
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

static inline std::string __fetch_string(const std::ostream& os)
{
    return dynamic_cast<const std::stringstream*>(&os)->str();
}


static inline
std::ostream& operator << (std::ostream& os, const std::vector<double>& v)
{
    std::string sep = "";
    for (auto x: v) { os << sep << x; sep = " "; }
    return os;
}



#define MESSAGE(_expr_) __fetch_string(std::stringstream() << _expr_)
83
/*#define CREATE_MESSAGE(_var_, _channel_, _expr_) message_handle _var_(new message_struc {_channel_, MESSAGE(_expr_)});*/
Damien Leroux's avatar
Damien Leroux committed
84
85

#ifndef SPELL_UNSAFE_OUTPUT
86
#define CREATE_MESSAGE(_channel_, _what_) if (!msg_handler_t::quiet()) msg_handler_t::enqueue(message_handle{new message_struc {_channel_, _what_}});
Damien Leroux's avatar
Damien Leroux committed
87
#else
88
#define CREATE_MESSAGE(_channel_, _what_) if (!msg_handler_t::quiet()) switch(_channel_) { \
Damien Leroux's avatar
Damien Leroux committed
89
90
91
92
93
94
95
96
97
98
99
        case msg_channel::Out: \
            std::cout << _what_; \
            break; \
        case msg_channel::Err: \
            std::cerr << _what_; \
            break; \
        case msg_channel::Log: \
            std::clog << _what_; \
            break; \
    };
#endif
100
101


102
103
104
105
106
107
struct DirectOutputIsForbidden : public std::runtime_error {
    DirectOutputIsForbidden()
        : std::runtime_error("Direct output to std::cerr, cour, or clog is forbidden. Use CREATE_MESSAGE(channel, MESSAGE(expression)) instead.")
    {}
};

108
109

struct ostream_manager {
110
111
    /* FIXME indent/dedent must be managed by this very class... Maybe use \x1 for indent and \x2 for dedent... */

112
113
114
115
    /* inspired from http://stackoverflow.com/questions/22042414/c-stream-insert-string-after-newline */
    class HeaderInserter : public std::streambuf {
        std::streambuf* dest;
        bool start_of_line;
116
117
118
119
        /*int indent;*/
        std::vector<std::string> prefix;
        bool prefix_insert_mode;
        std::stringstream prefix_ss;
120
121
122
123
124
125
126
    protected:
        int overflow(int ch) override;

    public:
        HeaderInserter(std::streambuf* dest)
            : dest(dest)
            , start_of_line(true)
127
128
129
130
            /*, indent(0)*/
            , prefix()
            , prefix_insert_mode(false)
            , prefix_ss()
131
        {}
132
133

        void rdbuf(std::streambuf* rb) { dest = rb; }
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
    };

    class ForbidOutput : public std::streambuf {
    protected:
        int overflow(int) override
        {
            /* Direct output is forbidden */
            /*throw std::ios_base::failure("Direct output is forbidden");*/
            abort();
        }
    };

    std::streambuf* old_cout_rdbuf;
    std::streambuf* old_clog_rdbuf;
    std::streambuf* old_cerr_rdbuf;
    HeaderInserter hi;
    ForbidOutput forbid;
    std::ostream cerr, cout, clog;

    ostream_manager()
        : old_cout_rdbuf(std::cout.rdbuf())
        , old_clog_rdbuf(std::clog.rdbuf())
        , old_cerr_rdbuf(std::cerr.rdbuf())
        , hi(old_cout_rdbuf)
        , forbid()
        , cerr(old_cerr_rdbuf), cout(old_cout_rdbuf), clog(&hi)
    {
161
#ifndef SPELL_UNSAFE_OUTPUT
162
163
164
        std::clog.rdbuf(&forbid);
        std::cout.rdbuf(&forbid);
        std::cerr.rdbuf(&forbid);
165
166
#else
        std::clog.rdbuf(&hi);
167
#endif
168
169
170
171
    }

    ~ostream_manager()
    {
172
#ifndef SPELL_UNSAFE_OUTPUT
173
174
175
        std::clog.rdbuf(old_clog_rdbuf);
        std::cout.rdbuf(old_cout_rdbuf);
        std::cerr.rdbuf(old_cerr_rdbuf);
176
#endif
177
    }
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

    void
        redirect(std::ostream& os)
        {
            hi.rdbuf(os.rdbuf());
            cerr.rdbuf(os.rdbuf());
            cout.rdbuf(os.rdbuf());
        }

    void
        restore()
        {
            hi.rdbuf(old_clog_rdbuf);
            cerr.rdbuf(old_cerr_rdbuf);
            cout.rdbuf(old_cout_rdbuf);
        }
194
195
196
197
198
199
200
201
};

struct message_queue : public ostream_manager {
    typedef std::mutex mutex_type;
    typedef std::unique_lock<mutex_type> scoped_lock_type;

    std::deque<message_handle> m_queue;
    mutex_type m_mutex;
202
    mutex_type m_stream_mutex;
203
    std::condition_variable m_condition;
Damien Leroux's avatar
Damien Leroux committed
204
    std::condition_variable m_flush_condition;
205

206
207
208
    static void catch_SIGSEG(int signo);
    static sighandler_t& old_sighandler() { static sighandler_t _; return _; }

209
210
211
212
213
214
215
216
217
218
219
220
    bool m_stop;

    std::thread m_thread;

    message_queue()
        : ostream_manager()
        , m_queue()
        , m_mutex()
        , m_condition()
        , m_stop(false)
        , m_thread([this] () { run(); })
    {
221
        /*old_sighandler() = signal(SIGSEGV, catch_SIGSEG);*/
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    }

    ~message_queue()
    {
        {
            scoped_lock_type lock(m_mutex);
            m_stop = true;
        }
        m_condition.notify_one();
        m_thread.join();
    }

    void enqueue(const message_handle& mh)
    {
        scoped_lock_type slt(m_mutex);
        m_queue.push_back(mh);
        m_condition.notify_one();
    }

241
242
    void lock_stream() { m_stream_mutex.lock(); }
    void unlock_stream() { m_stream_mutex.unlock(); }
243

244
    void run();
Damien Leroux's avatar
Damien Leroux committed
245
246
247
248
249
250
251
252

    void wait_for_flush()
    {
        scoped_lock_type lock(m_mutex);
        while (!m_queue.empty()) {
            m_flush_condition.wait(lock);
        }
    }
253
254
255
};


256
struct msg_handler_t {
257
#ifdef MSG_HANDLER_IS_SYNCED
258
259
    typedef std::recursive_mutex lock_type;
    typedef std::unique_lock<lock_type> scoped_lock_type;
260
261
262
263
264
265
266
267
268
#else
    typedef struct {
        void lock() {}
        void unlock() {}
    } lock_type;
    typedef struct _slt {
        _slt(lock_type&) {}
    } scoped_lock_type;
#endif
269

270
271
272
273
    struct state_t {
        bool color;
        std::set<std::string> workarounds;
        int count;
274
        int debug_indent;
275
        bool debug_enabled;
276
        bool quiet;
277
        std::vector<std::function<void()>> hooks;
278
        message_queue queue;
279
280
281
282
283
284

        const char* error() { ++count; return color ? _RED : ""; }
        const char* warning() { return color ? _YELLOW : ""; }
        const char* info() { return color ? _CYAN : ""; }
        const char* normal() { return color ? _NORMAL : ""; }

285
        state_t()
286
            : color(!!isatty(fileno(stdout))), workarounds(), count(0), debug_indent(0), debug_enabled(true), quiet(false), hooks()
287
            , queue()
288
        {
289
            /*std::cout << "Message handler instance created." << std::endl;*/
290
        }
291
292
		~state_t()
        {}
293
294
        void check(bool fatal);
        void reset();
295
        void run_hooks() { for (auto& f: hooks) { f(); } }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
    };

    static state_t& instance() { static state_t _; return _; }

    static void set_color(bool _) { instance().color = _; }
    static bool color() { return instance().color; }

    static const char* e() { return instance().error(); }
    static const char* w() { return instance().warning(); }
    static const char* i() { return instance().info(); }
    static const char* n() { return instance().normal(); }

    static void check(bool fatal) { instance().check(fatal); }
    static void reset() { instance().reset(); }
310

311
312
313
    static void hook(std::function<void()>&& f) { instance().hooks.push_back(f); }
    static void run_hooks() { instance().run_hooks(); }

314
315
316
317
    static void indent() { instance().debug_indent += 3; }
    static void dedent() { instance().debug_indent -= 3; }
    static int get_indent() { return instance().debug_indent; }

318
    static void enqueue(const message_handle& mh) { instance().queue.enqueue(mh); }
Damien Leroux's avatar
Damien Leroux committed
319
    static void wait_for_flush() { instance().queue.wait_for_flush(); }
320

Damien Leroux's avatar
WIP.    
Damien Leroux committed
321
322
323
324
325
326
327
    static int termcols()
    {
        struct winsize w;
        ioctl(0, TIOCGWINSZ, &w);
        return w.ws_col;
    }

328
    static bool& debug_enabled() { return instance().debug_enabled; }
329
    static bool& quiet() { return instance().quiet; }
330

331
    static lock_type mutex;
332
333
334

    static void redirect(std::ostream& os) { instance().queue.redirect(os); }
    static void restore() { instance().queue.restore(); }
335
336
};

337
338
339
340
inline
int ostream_manager::HeaderInserter::overflow(int ch)
{
    int retval = 0;
341
    if (ch == _DBG_INDENT_MARK) {
342
343
344
345
346
347
348
349
        /*indent += 3;*/
        prefix_insert_mode = !prefix_insert_mode;
        if (prefix_insert_mode) {
            prefix_ss.clear();
            prefix_ss.str(std::string());
        } else {
            prefix.push_back(prefix_ss.str());
        }
350
    } else if (ch == _DBG_DEDENT_MARK) {
351
        if (prefix_insert_mode) {
352
            overflow(_DBG_INDENT_MARK);
353
354
355
356
357
358
359
        }
        if (prefix.size()) {
            prefix.pop_back();
        }
        /*indent -= 3 * (indent > 0);*/
    } else if (prefix_insert_mode) {
        prefix_ss << (char) ch;
360
    } else if (ch != traits_type::eof()) {
361
        if (start_of_line) {
362
363
364
365
            for (const std::string& s: prefix) {
                for (const char c: s) {
                    dest->sputc(c);
                }
366
            }
367
368
369
            /*for (int i = 0; i < indent; ++i) {*/
                /*dest->sputc(' ');*/
            /*}*/
370
371
372
373
374
375
376
        }
        retval = dest->sputc( ch );
        start_of_line = ch == '\n';
    }
    return retval;
}

377
378
379
inline
void message_queue::run()
{
Damien Leroux's avatar
Damien Leroux committed
380
    message_handle  next;
381
382
    while (true)
    {
Damien Leroux's avatar
Damien Leroux committed
383
384
        {
            scoped_lock_type lock(m_mutex);
385

Damien Leroux's avatar
Damien Leroux committed
386
            m_condition.wait(lock, [this]() { return m_stop || !m_queue.empty(); });
387

Damien Leroux's avatar
Damien Leroux committed
388
389
390
            /*while (!m_stop && m_queue.empty()) {*/
                /*m_condition.wait(lock);*/
            /*}*/
391

Damien Leroux's avatar
Damien Leroux committed
392
393
394
            if (m_stop && m_queue.empty()) {
                return;
            }
395

Damien Leroux's avatar
Damien Leroux committed
396
397
            next = m_queue.front();
            m_queue.pop_front();
398

Damien Leroux's avatar
Damien Leroux committed
399
        }
400
        if (next->message == _DBG_SYNC_MARK_S) {
Damien Leroux's avatar
Damien Leroux committed
401
402
403
            m_flush_condition.notify_one();
            continue;
        } else if (next->message.size() == 0) {
404
405
            continue;
        }
Damien Leroux's avatar
Damien Leroux committed
406
407
408
        std::ostream& channel = next->channel == msg_channel::Out ? cout
                              : next->channel == msg_channel::Log ? clog
                              : cerr;
409
410
411
412
413
414
415
        lock_stream();
        channel << next->message << std::flush;
        unlock_stream();
        msg_handler_t::run_hooks();
    }
}

416
417
418
419
420
421
422
423
424
425
#ifdef SPELL_UNSAFE_OUTPUT

#define MSG_ERROR(_msg_expr_, _workaround_expr_) do { std::cerr << _msg_expr_ << std::endl; } while (0)
#define MSG_WARNING(_msg_expr_)  do { std::cout << _msg_expr_ << std::endl; } while (0)
#define MSG_INFO(_msg_expr_) do { std::cout << _msg_expr_ << std::endl; } while (0)
#define MSG_QUEUE_FLUSH()
#define MSG_DEBUG(_msg_expr_) do { std::clog << _msg_expr_ << std::endl; } while (0)

#else

426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#define MSG_ERROR(_msg_expr_, _workaround_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(msg_handler_t::e() << "[ERR] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
        std::stringstream s; s << _workaround_expr_;\
        if (s.str().size()) { msg_handler_t::instance().workarounds.insert(s.str()); }\
} while (0)

#define MSG_WARNING(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Out, MESSAGE(msg_handler_t::w() << "[WRN] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
} while (0)

#define MSG_INFO(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Out, MESSAGE(msg_handler_t::i() << "[MSG] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
} while (0)

443
444
445
446
447
#define MSG_QUEUE_FLUSH() do { \
    CREATE_MESSAGE(msg_channel::Log, _DBG_SYNC_MARK_S); \
    msg_handler_t::wait_for_flush(); \
} while (0)

448
449
#define MSG_DEBUG(_msg_expr_) \
    do {\
450
451
452
453
        if (msg_handler_t::debug_enabled()) {\
            CREATE_MESSAGE(msg_channel::Log, MESSAGE(_msg_expr_ << std::endl));\
        }\
    } while (0);
454

455
#endif
Damien Leroux's avatar
Damien Leroux committed
456

457
#if 0
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
#define MSG_ERROR(_msg_expr_, _workaround_expr_) \
    do {\
        {msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cerr << msg_handler_t::e() << "[ERR] " << _msg_expr_ << msg_handler_t::n() << std::endl;}\
        std::stringstream s; s << _workaround_expr_;\
        if (s.str().size()) { msg_handler_t::instance().workarounds.insert(s.str()); }\
        msg_handler_t::run_hooks();\
} while (0)

#define MSG_WARNING(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cerr << msg_handler_t::w() << "[WRN] " << _msg_expr_ << msg_handler_t::n() << std::endl;\
        msg_handler_t::run_hooks();\
    } while(0)

#define MSG_INFO(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cout << msg_handler_t::i() << "[MSG] " << _msg_expr_ << msg_handler_t::n() << std::endl;\
        msg_handler_t::run_hooks();\
    } while(0)

#define MSG_DEBUG(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
484
        std::clog << _msg_expr_ << std::endl;\
485
486
        msg_handler_t::run_hooks();\
    } while(0)
487
#endif
488

489
490
491
#define MSG_DEBUG_INDENT CREATE_MESSAGE(msg_channel::Log, _DBG_INDENT_MARK_S "   " _DBG_INDENT_MARK_S)
#define MSG_DEBUG_INDENT_EXPR(_str_) CREATE_MESSAGE(msg_channel::Log, MESSAGE(_DBG_INDENT_MARK << _str_ << _DBG_INDENT_MARK))
#define MSG_DEBUG_DEDENT CREATE_MESSAGE(msg_channel::Log, _DBG_DEDENT_MARK_S)
492

493

494
495
496
/*#ifndef _SPELL_ERROR_H_MSGQ_CATCH_SIGSEG_*/
/*#define _SPELL_ERROR_H_MSGQ_CATCH_SIGSEG_*/
inline
497
498
499
500
501
502
503
void message_queue::catch_SIGSEG(int)
{
    MSG_DEBUG("**SEGFAULT DETECTED**");
    MSG_QUEUE_FLUSH();
    signal(SIGSEGV, old_sighandler());
    raise(SIGSEGV);
}
504
/*#endif*/
505

506
507
508
509
510
511
struct scoped_indent {
    scoped_indent() { MSG_DEBUG_INDENT; }
    scoped_indent(const std::string& str) { MSG_DEBUG_INDENT_EXPR(str); }
    ~scoped_indent() { MSG_DEBUG_DEDENT; }
};

512

513
514
inline void msg_handler_t::state_t::check(bool fatal)
{
515
    msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);
516
    if (count > 0) {
517
518
519
520
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(
                info() << "[MSG] " << count << " error"
                << (count > 1 ? "s were" : " was")
                << " reported. Suggestions to fix this:" << std::endl));
521
        for (auto& w: workarounds) {
522
            CREATE_MESSAGE(msg_channel::Err, MESSAGE(info() << "      - " << w << normal() << std::endl));
523
524
        }
        if (fatal) {
525
            CREATE_MESSAGE(msg_channel::Out, MESSAGE(normal() <<"At least one fatal error encountered. Aborting process." << std::endl));
526
527
528
529
530
531
532
533
534
535
            exit(-count);
        } else {
            reset();
        }
    }
}

inline void msg_handler_t::state_t::reset()
{
    if (workarounds.size()) {
536
        MSG_WARNING(workarounds.size() << " workarounds silently discarded");
537
538
539
540
541
542
543
    }
    count = 0;
    workarounds.clear();
}

#define WHITE (msg_handler_t::instance().color ? _WHITE : "")
#define YELLOW (msg_handler_t::instance().color ? _YELLOW : "")
544
#define GREEN (msg_handler_t::instance().color ? _GREEN : "")
545
546
547
548
#define RED (msg_handler_t::instance().color ? _RED : "")
#define CYAN (msg_handler_t::instance().color ? _CYAN : "")
#define NORMAL (msg_handler_t::instance().color ? _NORMAL : "")

549
550
551
552
553
554
#ifdef NDEBUG
#define DUMP_FILE_LINE()
#else
#define DUMP_FILE_LINE() MSG_DEBUG(__FILE__ << ':' << __LINE__)
#endif

555
556
#endif