1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_WORK_GUARD_HPP
10  
#ifndef BOOST_CAPY_WORK_GUARD_HPP
11  
#define BOOST_CAPY_WORK_GUARD_HPP
11  
#define BOOST_CAPY_WORK_GUARD_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/ex/execution_context.hpp>
14  
#include <boost/capy/ex/execution_context.hpp>
15  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
16  

16  

17  
#include <utility>
17  
#include <utility>
18  

18  

19  
namespace boost {
19  
namespace boost {
20  
namespace capy {
20  
namespace capy {
21  

21  

22  
/** RAII guard that keeps an executor's context from completing.
22  
/** RAII guard that keeps an executor's context from completing.
23  

23  

24  
    This class holds "work" on an executor, preventing the associated
24  
    This class holds "work" on an executor, preventing the associated
25  
    execution context's `run()` function from returning due to lack of
25  
    execution context's `run()` function from returning due to lack of
26  
    work. It calls `on_work_started()` on construction and
26  
    work. It calls `on_work_started()` on construction and
27  
    `on_work_finished()` on destruction, ensuring proper work tracking.
27  
    `on_work_finished()` on destruction, ensuring proper work tracking.
28  

28  

29  
    The guard is useful when you need to keep an execution context
29  
    The guard is useful when you need to keep an execution context
30  
    running while waiting for external events or when work will be
30  
    running while waiting for external events or when work will be
31  
    posted later.
31  
    posted later.
32  

32  

33  
    @par RAII Semantics
33  
    @par RAII Semantics
34  

34  

35  
    @li Construction calls `ex.on_work_started()`.
35  
    @li Construction calls `ex.on_work_started()`.
36  
    @li Destruction calls `ex.on_work_finished()` if `owns_work()`.
36  
    @li Destruction calls `ex.on_work_finished()` if `owns_work()`.
37  
    @li Copy construction creates a new work reference (calls
37  
    @li Copy construction creates a new work reference (calls
38  
        `on_work_started()` again).
38  
        `on_work_started()` again).
39  
    @li Move construction transfers ownership without additional calls.
39  
    @li Move construction transfers ownership without additional calls.
40  

40  

41  
    @par Thread Safety
41  
    @par Thread Safety
42  

42  

43  
    Distinct objects may be accessed concurrently. Access to a single
43  
    Distinct objects may be accessed concurrently. Access to a single
44  
    object requires external synchronization.
44  
    object requires external synchronization.
45  

45  

46  
    @par Example
46  
    @par Example
47  
    @code
47  
    @code
48  
    io_context ctx;
48  
    io_context ctx;
49  

49  

50  
    // Keep context running while we set things up
50  
    // Keep context running while we set things up
51  
    auto guard = make_work_guard(ctx);
51  
    auto guard = make_work_guard(ctx);
52  

52  

53  
    std::thread t([&ctx]{ ctx.run(); });
53  
    std::thread t([&ctx]{ ctx.run(); });
54  

54  

55  
    // ... post work to ctx ...
55  
    // ... post work to ctx ...
56  

56  

57  
    // Allow context to complete when work is done
57  
    // Allow context to complete when work is done
58  
    guard.reset();
58  
    guard.reset();
59  

59  

60  
    t.join();
60  
    t.join();
61  
    @endcode
61  
    @endcode
62  

62  

63  
    @note The executor is returned by reference, allowing callers to
63  
    @note The executor is returned by reference, allowing callers to
64  
    manage the executor's lifetime directly. This is essential in
64  
    manage the executor's lifetime directly. This is essential in
65  
    coroutine-first designs where the executor often outlives individual
65  
    coroutine-first designs where the executor often outlives individual
66  
    coroutine frames.
66  
    coroutine frames.
67  

67  

68  
    @tparam Ex A type satisfying the Executor concept.
68  
    @tparam Ex A type satisfying the Executor concept.
69  

69  

70  
    @see make_work_guard, Executor
70  
    @see make_work_guard, Executor
71  
*/
71  
*/
72  
template<Executor Ex>
72  
template<Executor Ex>
73  
class work_guard
73  
class work_guard
74  
{
74  
{
75  
    Ex ex_;
75  
    Ex ex_;
76  
    bool owns_;
76  
    bool owns_;
77  

77  

78  
public:
78  
public:
79  
    /** The underlying executor type. */
79  
    /** The underlying executor type. */
80  
    using executor_type = Ex;
80  
    using executor_type = Ex;
81  

81  

82  
    /** Construct a work guard.
82  
    /** Construct a work guard.
83  

83  

84  
        Calls `ex.on_work_started()` to inform the executor that
84  
        Calls `ex.on_work_started()` to inform the executor that
85  
        work is outstanding.
85  
        work is outstanding.
86  

86  

87  
        @par Exception Safety
87  
        @par Exception Safety
88  
        No-throw guarantee.
88  
        No-throw guarantee.
89  

89  

90  
        @par Postconditions
90  
        @par Postconditions
91  
        @li `owns_work() == true`
91  
        @li `owns_work() == true`
92  
        @li `executor() == ex`
92  
        @li `executor() == ex`
93  

93  

94  
        @param ex The executor to hold work on. Moved into the guard.
94  
        @param ex The executor to hold work on. Moved into the guard.
95  
    */
95  
    */
96  
    explicit
96  
    explicit
97  
    work_guard(Ex ex) noexcept
97  
    work_guard(Ex ex) noexcept
98  
        : ex_(std::move(ex))
98  
        : ex_(std::move(ex))
99  
        , owns_(true)
99  
        , owns_(true)
100  
    {
100  
    {
101  
        ex_.on_work_started();
101  
        ex_.on_work_started();
102  
    }
102  
    }
103  

103  

104  
    /** Copy constructor.
104  
    /** Copy constructor.
105  

105  

106  
        Creates a new work guard holding work on the same executor.
106  
        Creates a new work guard holding work on the same executor.
107  
        Calls `on_work_started()` on the executor.
107  
        Calls `on_work_started()` on the executor.
108  

108  

109  
        @par Exception Safety
109  
        @par Exception Safety
110  
        No-throw guarantee.
110  
        No-throw guarantee.
111  

111  

112  
        @par Postconditions
112  
        @par Postconditions
113  
        @li `owns_work() == other.owns_work()`
113  
        @li `owns_work() == other.owns_work()`
114  
        @li `executor() == other.executor()`
114  
        @li `executor() == other.executor()`
115  

115  

116  
        @param other The work guard to copy from.
116  
        @param other The work guard to copy from.
117  
    */
117  
    */
118  
    work_guard(work_guard const& other) noexcept
118  
    work_guard(work_guard const& other) noexcept
119  
        : ex_(other.ex_)
119  
        : ex_(other.ex_)
120  
        , owns_(other.owns_)
120  
        , owns_(other.owns_)
121  
    {
121  
    {
122  
        if(owns_)
122  
        if(owns_)
123  
            ex_.on_work_started();
123  
            ex_.on_work_started();
124  
    }
124  
    }
125  

125  

126  
    /** Move constructor.
126  
    /** Move constructor.
127  

127  

128  
        Transfers work ownership from `other` to `*this`. Does not
128  
        Transfers work ownership from `other` to `*this`. Does not
129  
        call `on_work_started()` or `on_work_finished()`.
129  
        call `on_work_started()` or `on_work_finished()`.
130  

130  

131  
        @par Exception Safety
131  
        @par Exception Safety
132  
        No-throw guarantee.
132  
        No-throw guarantee.
133  

133  

134  
        @par Postconditions
134  
        @par Postconditions
135  
        @li `owns_work()` equals the prior value of `other.owns_work()`
135  
        @li `owns_work()` equals the prior value of `other.owns_work()`
136  
        @li `other.owns_work() == false`
136  
        @li `other.owns_work() == false`
137  

137  

138  
        @param other The work guard to move from.
138  
        @param other The work guard to move from.
139  
    */
139  
    */
140  
    work_guard(work_guard&& other) noexcept
140  
    work_guard(work_guard&& other) noexcept
141  
        : ex_(std::move(other.ex_))
141  
        : ex_(std::move(other.ex_))
142  
        , owns_(other.owns_)
142  
        , owns_(other.owns_)
143  
    {
143  
    {
144  
        other.owns_ = false;
144  
        other.owns_ = false;
145  
    }
145  
    }
146  

146  

147  
    /** Destructor.
147  
    /** Destructor.
148  

148  

149  
        If `owns_work()` is `true`, calls `on_work_finished()` on
149  
        If `owns_work()` is `true`, calls `on_work_finished()` on
150  
        the executor.
150  
        the executor.
151  

151  

152  
        @par Exception Safety
152  
        @par Exception Safety
153  
        No-throw guarantee.
153  
        No-throw guarantee.
154  
    */
154  
    */
155  
    ~work_guard()
155  
    ~work_guard()
156  
    {
156  
    {
157  
        if(owns_)
157  
        if(owns_)
158  
            ex_.on_work_finished();
158  
            ex_.on_work_finished();
159  
    }
159  
    }
160  

160  

161  
    work_guard& operator=(work_guard const&) = delete;
161  
    work_guard& operator=(work_guard const&) = delete;
162  

162  

163  
    /** Return the underlying executor by reference.
163  
    /** Return the underlying executor by reference.
164  

164  

165  
        The reference remains valid for the lifetime of this guard,
165  
        The reference remains valid for the lifetime of this guard,
166  
        enabling callers to manage executor lifetime explicitly.
166  
        enabling callers to manage executor lifetime explicitly.
167  

167  

168  
        @par Exception Safety
168  
        @par Exception Safety
169  
        No-throw guarantee.
169  
        No-throw guarantee.
170  

170  

171  
        @return A reference to the stored executor.
171  
        @return A reference to the stored executor.
172  
    */
172  
    */
173  
    executor_type const&
173  
    executor_type const&
174  
    executor() const noexcept
174  
    executor() const noexcept
175  
    {
175  
    {
176  
        return ex_;
176  
        return ex_;
177  
    }
177  
    }
178  

178  

179  
    /** Return whether the guard owns work.
179  
    /** Return whether the guard owns work.
180  

180  

181  
        @par Exception Safety
181  
        @par Exception Safety
182  
        No-throw guarantee.
182  
        No-throw guarantee.
183  

183  

184  
        @return `true` if this guard will call `on_work_finished()`
184  
        @return `true` if this guard will call `on_work_finished()`
185  
            on destruction, `false` otherwise.
185  
            on destruction, `false` otherwise.
186  
    */
186  
    */
187  
    bool
187  
    bool
188  
    owns_work() const noexcept
188  
    owns_work() const noexcept
189  
    {
189  
    {
190  
        return owns_;
190  
        return owns_;
191  
    }
191  
    }
192  

192  

193  
    /** Release ownership of the work.
193  
    /** Release ownership of the work.
194  

194  

195  
        If `owns_work()` is `true`, calls `on_work_finished()` on
195  
        If `owns_work()` is `true`, calls `on_work_finished()` on
196  
        the executor and sets ownership to `false`. Otherwise, has
196  
        the executor and sets ownership to `false`. Otherwise, has
197  
        no effect.
197  
        no effect.
198  

198  

199  
        @par Exception Safety
199  
        @par Exception Safety
200  
        No-throw guarantee.
200  
        No-throw guarantee.
201  

201  

202  
        @par Postconditions
202  
        @par Postconditions
203  
        @li `owns_work() == false`
203  
        @li `owns_work() == false`
204  
    */
204  
    */
205  
    void
205  
    void
206  
    reset() noexcept
206  
    reset() noexcept
207  
    {
207  
    {
208  
        if(owns_)
208  
        if(owns_)
209  
        {
209  
        {
210  
            ex_.on_work_finished();
210  
            ex_.on_work_finished();
211  
            owns_ = false;
211  
            owns_ = false;
212  
        }
212  
        }
213  
    }
213  
    }
214  
};
214  
};
215  

215  

216  
//------------------------------------------------
216  
//------------------------------------------------
217  

217  

218  
/** Create a work guard from an executor.
218  
/** Create a work guard from an executor.
219  

219  

220  
    @par Exception Safety
220  
    @par Exception Safety
221  
    No-throw guarantee.
221  
    No-throw guarantee.
222  

222  

223  
    @param ex The executor to create the guard for.
223  
    @param ex The executor to create the guard for.
224  

224  

225  
    @return A `work_guard` holding work on `ex`.
225  
    @return A `work_guard` holding work on `ex`.
226  

226  

227  
    @see work_guard
227  
    @see work_guard
228  
*/
228  
*/
229  
template<Executor Ex>
229  
template<Executor Ex>
230  
work_guard<Ex>
230  
work_guard<Ex>
231  
make_work_guard(Ex ex)
231  
make_work_guard(Ex ex)
232  
{
232  
{
233  
    return work_guard<Ex>(std::move(ex));
233  
    return work_guard<Ex>(std::move(ex));
234  
}
234  
}
235  

235  

236  
} // capy
236  
} // capy
237  
} // boost
237  
} // boost
238  

238  

239  
#endif
239  
#endif