summaryrefslogtreecommitdiff
path: root/src/lib/util/threads/thread.cc
blob: 54f67da6c86974e337592998b24f352bdaefafa3 (plain)
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
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <util/threads/thread.h>
#include <util/threads/sync.h>

#include <memory>
#include <string>
#include <cstring>
#include <cerrno>

#include <pthread.h>
#include <signal.h>

#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>

using std::string;
using std::exception;
using std::unique_ptr;
using boost::scoped_ptr;

namespace isc {
namespace util {
namespace thread {

namespace {

// Signal blocker class.
class Blocker : boost::noncopyable {
public:
    // Constructor blocks all signals
    Blocker() {
        sigset_t new_mask;
        sigfillset(&new_mask);
        pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask_);
    }

    // Destructor restores the previous signal mask
    ~Blocker() {
        pthread_sigmask(SIG_SETMASK, &old_mask_, 0);
    }

private:
    // The previous signal mask
    sigset_t old_mask_;
};

}

// The implementation of the Thread class.
//
// This internal state is not deleted until the thread terminates and is either
// waited for or detached. We could do this with shared_ptr (or, shared_ptr and
// weak_ptr), but we plan on compiling boost without thread support, so it
// might not be safe. Therefore we use an explicit mutex. It is being locked
// only 2-3 times in the lifetime of the thread, which should be negligible
// overhead anyway.
class Thread::Impl {
public:
    Impl(const boost::function<void ()>& main) :
        // Two things to happen before destruction - thread needs to terminate
        // and the creating thread needs to release it.
        waiting_(2),
        main_(main),
        exception_(false),
        tid_(0)
    {}
    // Another of the waiting events is done. If there are no more, delete
    // impl.
    static void done(Impl* impl) {
        bool should_delete(false);
        { // We need to make sure the mutex is unlocked before it is deleted
            Mutex::Locker locker(impl->mutex_);
            if (--impl->waiting_ == 0) {
                should_delete = true;
            }
        }
        if (should_delete) {
            delete impl;
        }
    }
    // Run the thread. The type of parameter is because the pthread API.
    static void* run(void* impl_raw) {
        Impl* impl = static_cast<Impl*>(impl_raw);
        try {
            impl->main_();
        } catch (const exception& e) {
            impl->exception_ = true;
            impl->exception_text_ = e.what();
        } catch (...) {
            impl->exception_ = true;
            impl->exception_text_ = "Unknown exception";
        }
        done(impl);
        return (NULL);
    }
    // How many events are waiting? One is for the thread to finish, one
    // for the destructor of Thread or wait. Once both happen, this is
    // no longer needed.
    size_t waiting_;
    // The main function of the thread.
    boost::function<void ()> main_;
    // Was there an exception?
    bool exception_;
    string exception_text_;
    // The mutex protects the waiting_ member, which ensures there are
    // no race conditions and collisions when terminating. The other members
    // should be safe, because:
    // * tid_ is read only.
    // * exception_ and exception_text_ is accessed outside of the thread
    //   only after join, by that time the thread must have terminated.
    // * main_ is used in a read-only way here. If there are any shared
    //   resources used inside, it is up to the main_ itself to take care.
    Mutex mutex_;
    // Which thread are we talking about anyway?
    pthread_t tid_;
};

Thread::Thread(const boost::function<void ()>& main) :
    impl_(NULL)
{
    unique_ptr<Impl> impl(new Impl(main));
    Blocker blocker;
    const int result = pthread_create(&impl->tid_, NULL, &Impl::run,
                                      impl.get());
    // Any error here?
    switch (result) {
        case 0: // All 0K
            impl_ = impl.release();
            break;
        case EAGAIN:
            throw std::bad_alloc();
        default: // Other errors. They should not happen.
            isc_throw(isc::InvalidOperation, std::strerror(result));
    }
}

Thread::~Thread() {
    if (impl_ != NULL) {
        // In case we didn't call wait yet
        const int result = pthread_detach(impl_->tid_);
        Impl::done(impl_);
        impl_ = NULL;
        // If the detach ever fails, something is screwed rather badly.
        assert(result == 0);
    }
}

void
Thread::wait() {
    if (impl_ == NULL) {
        isc_throw(isc::InvalidOperation,
                  "Wait called and no thread to wait for");
    }

    const int result = pthread_join(impl_->tid_, NULL);
    if (result != 0) {
        isc_throw(isc::InvalidOperation, std::strerror(result));
    }

    // Was there an exception in the thread?
    scoped_ptr<UncaughtException> ex;
    // Something here could in theory throw. But we already terminated the thread, so
    // we need to make sure we are in consistent state even in such situation (like
    // releasing the mutex and impl_).
    try {
        if (impl_->exception_) {
            ex.reset(new UncaughtException(__FILE__, __LINE__,
                                           impl_->exception_text_.c_str()));
        }
    } catch (...) {
        Impl::done(impl_);
        impl_ = NULL;
        // We have eaten the UncaughtException by now, but there's another
        // exception instead, so we have at least something.
        throw;
    }

    Impl::done(impl_);
    impl_ = NULL;
    if (ex.get() != NULL) {
        throw UncaughtException(*ex);
    }
}

}
}
}