summaryrefslogtreecommitdiffstats
path: root/src/lib/util/threads/sync.h
blob: 9777041278aded8fb48e527f0cbe16718c893f4a (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
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
// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#ifndef B10_THREAD_SYNC_H
#define B10_THREAD_SYNC_H

#include <exceptions/exceptions.h>

#include <boost/noncopyable.hpp>

#include <cstdlib> // for NULL.

namespace isc {
namespace util {
namespace thread {
class CondVar;

/// \brief Mutex with very simple interface
///
/// Since mutexes are very system dependant, we create our own wrapper around
/// whatever is available on the system and hide it.
///
/// To use this mutex, create it and then lock and unlock it by creating the
/// Mutex::Locker object.
///
/// Also, as mutex is a low-level system object, an error might happen at any
/// operation with it. We convert many errors to the isc::InvalidOperation,
/// since the errors usually happen only when used in a wrong way. Any methods
/// or constructors in this class can throw. Allocation errors are converted
/// to std::bad_alloc (for example when OS-dependant limit of mutexes is
/// exceeded). Some errors which usually mean a programmer error abort the
/// program, since there could be no safe way to recover from them.
///
/// The current interface is somewhat minimalistic. If we ever need more, we
/// can add it later.
class Mutex : boost::noncopyable {
public:
    /// \brief Constructor.
    ///
    /// Creates a mutex. It is a non-recursive mutex (can be locked just once,
    /// if the same threads tries to lock it again, Bad Things Happen).
    ///
    /// Depending on compilation parameters and OS, the mutex may or may not
    /// do some error and sanity checking. However, such checking is meant
    /// only to aid development, not rely on it as a feature.
    ///
    /// \throw std::bad_alloc In case allocation of something (memory, the
    ///     OS mutex) fails.
    /// \throw isc::InvalidOperation Other unspecified errors around the mutex.
    ///     This should be rare.
    Mutex();

    /// \brief Destructor.
    ///
    /// Destroys the mutex. It is not allowed to destroy a mutex which is
    /// currently locked. This means a Locker created with this Mutex must
    /// never live longer than the Mutex itself.
    ~Mutex();

    /// \brief This holds a lock on a Mutex.
    ///
    /// To lock a mutex, create a locker. It'll get unlocked when the locker
    /// is destroyed.
    ///
    /// If you create the locker on the stack or using some other "garbage
    /// collecting" mechanism (auto_ptr, for example), it ensures exception
    /// safety with regards to the mutex - it'll get released on the exit
    /// of function no matter by what means.
    class Locker : boost::noncopyable {
    public:
        /// \brief Exception thrown when the mutex is already locked and
        ///     a non-blocking locker is attempted around it.
        struct AlreadyLocked : public isc::InvalidParameter {
            AlreadyLocked(const char* file, size_t line, const char* what) :
                isc::InvalidParameter(file, line, what)
            {}
        };

        /// \brief Constructor.
        ///
        /// Locks the mutex. May block for extended period of time if
        /// \c block is true.
        ///
        /// \throw isc::InvalidOperation when OS reports error. This usually
        ///     means an attempt to use the mutex in a wrong way (locking
        ///     a mutex second time from the same thread, for example).
        /// \throw AlreadyLocked if \c block is false and the mutex is
        ///     already locked.
        Locker(Mutex& mutex, bool block = true) :
            mutex_(mutex)
        {
            if (block) {
                mutex.lock();
            } else {
                if (!mutex.tryLock()) {
                    isc_throw(AlreadyLocked, "The mutex is already locked");
                }
            }
        }

        /// \brief Destructor.
        ///
        /// Unlocks the mutex.
        ~Locker() {
            mutex_.unlock();
        }
    private:
        Mutex& mutex_;
    };
    /// \brief If the mutex is currently locked
    ///
    /// This is debug aiding method only. And it might be unavailable in
    /// non-debug build (because keeping the state might be needlesly
    /// slow).
    ///
    /// \todo Disable in non-debug build
    bool locked() const;

private:
    /// \brief Lock the mutex
    ///
    /// This method blocks until the mutex can be locked.
    void lock();

    /// \brief Try to lock the mutex
    ///
    /// This method doesn't block and returns immediately with a status
    /// on whether the lock operation was successful.
    ///
    /// \return true if the lock was successful, false otherwise.
    bool tryLock();

    /// \brief Unlock the mutex
    void unlock();

private:
    friend class CondVar;

    // Commonly called after acquiring the lock, checking and updating
    // internal state for debug.
    //
    // Note that this method is only available when the build is
    // configured with debugging support.
    void postLockAction();

    // Commonly called before releasing the lock, checking and updating
    // internal state for debug.
    //
    // If throw_ok is true, it throws \c isc::InvalidOperation when the check
    // fails; otherwise it aborts the process.  This parameter must be set
    // to false if the call to this shouldn't result in an exception (e.g.
    // when called from a destructor).
    //
    // Note that this method is only available when the build is
    // configured with debugging support.
    void preUnlockAction(bool throw_ok);

    class Impl;
    Impl* impl_;
};

/// \brief Encapsulation for a condition variable.
///
/// This class provides a simple encapsulation of condition variable for
/// inter-thread synchronization.  It has similar but simplified interface as
/// that for \c pthread_cond_ variants.
///
/// It uses the \c Mutex class object for the mutex used with the condition
/// variable.  Since for normal applications the internal \c Mutex::Locker
/// class is the only available interface to acquire a lock, sample code
/// for waiting on a condition variable would look like this:
/// \code
/// CondVar cond;
/// Mutex mutex;
/// {
///     Mutex::Locker locker(mutex);
///     while (some_condition) {
///         cond.wait(mutex);
///     }
///     // do something under the protection of locker
/// }   // lock is released here
/// \endcode
/// Note that \c mutex passed to the \c wait() method must be the same one
/// used to construct the \c locker.
///
/// Right now there is no equivalent to pthread_cond_broadcast() or
/// pthread_cond_timedwait() in this class, because this class was meant
/// for internal development of BIND 10 and we don't need these at the
/// moment.  If and when we need these interfaces they can be added at that
/// point. Also, Kea likely to not use threading model, so the usefulness
/// of this class is uncertain.
///
/// \note This class is defined as a friend class of \c Mutex and directly
/// refers to and modifies private internals of the \c Mutex class.  It breaks
/// the assumption that the lock is only acquired or released via the
/// \c Locker class and breaks other integrity assumption on \c Mutex,
/// thereby making it more fragile, but we couldn't find other way to
/// implement a safe and still simple realization of condition variables.
/// So, this is a kind of compromise.  If this class is needed to be
/// extended, first consider a way to use public interfaces of \c Mutex;
/// do not easily rely on the fact that this class is a friend of it.
class CondVar : boost::noncopyable {
public:
    /// \brief Constructor.
    ///
    /// \throw std::bad_alloc memory allocation failure
    /// \throw isc::Unexpected other unexpected shortage of system resource
    CondVar();

    /// \brief Destructor.
    ///
    /// An object of this class must not be destroyed while some thread
    /// is waiting on it.  If this condition isn't met the destructor will
    /// terminate the program.
    ~CondVar();

    /// \brief Wait on the condition variable.
    ///
    /// This method works like \c pthread_cond_wait().  For mutex it takes
    /// an \c Mutex class object.  A lock for the mutex must have been
    /// acquired.  If this condition isn't met, it can throw an exception
    /// (in the debug mode build) or result in undefined behavior.
    ///
    /// The lock will be automatically released within this method, and
    /// will be re-acquired on the exit of this method.
    ///
    /// \throw isc::InvalidOperation mutex isn't locked
    /// \throw isc::BadValue mutex is not a valid \c Mutex object
    ///
    /// \param mutex A \c Mutex object to be released on wait().
    void wait(Mutex& mutex);

    /// \brief Unblock a thread waiting for the condition variable.
    ///
    /// This method wakes one of other threads (if any) waiting on this object
    /// via the \c wait() call.
    ///
    /// This method never throws; if some unexpected low level error happens
    /// it terminates the program.
    void signal();
private:
    class Impl;
    Impl* impl_;
};

} // namespace thread
} // namespace util
} // namespace isc

#endif

// Local Variables:
// mode: c++
// End: