1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // UNSUPPORTED: no-threads |
10 | // UNSUPPORTED: libcpp-has-no-experimental-stop_token |
11 | // UNSUPPORTED: c++03, c++11, c++14, c++17 |
12 | // XFAIL: availability-synchronization_library-missing |
13 | |
14 | // template<class F, class... Args> |
15 | // explicit jthread(F&& f, Args&&... args); |
16 | |
17 | #include <cassert> |
18 | #include <stop_token> |
19 | #include <thread> |
20 | #include <type_traits> |
21 | |
22 | #include "test_macros.h" |
23 | |
24 | template <class... Args> |
25 | struct Func { |
26 | void operator()(Args...) const; |
27 | }; |
28 | |
29 | // Constraints: remove_cvref_t<F> is not the same type as jthread. |
30 | static_assert(std::is_constructible_v<std::jthread, Func<>>); |
31 | static_assert(std::is_constructible_v<std::jthread, Func<int>, int>); |
32 | static_assert(!std::is_constructible_v<std::jthread, std::jthread const&>); |
33 | |
34 | // explicit |
35 | template <class T> |
36 | void conversion_test(T); |
37 | |
38 | template <class T, class... Args> |
39 | concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test<T>({std::forward<Args>(args)...}); }; |
40 | |
41 | static_assert(!ImplicitlyConstructible<std::jthread, Func<>>); |
42 | static_assert(!ImplicitlyConstructible<std::jthread, Func<int>, int>); |
43 | |
44 | int main(int, char**) { |
45 | // Effects: Initializes ssource |
46 | // Postconditions: get_id() != id() is true and ssource.stop_possible() is true |
47 | // and *this represents the newly started thread. |
48 | { |
49 | std::jthread jt{[] {}}; |
50 | assert(jt.get_stop_source().stop_possible()); |
51 | assert(jt.get_id() != std::jthread::id()); |
52 | } |
53 | |
54 | // The new thread of execution executes |
55 | // invoke(auto(std::forward<F>(f)), get_stop_token(), auto(std::forward<Args>(args))...) |
56 | // if that expression is well-formed, |
57 | { |
58 | int result = 0; |
59 | std::jthread jt{[&result](std::stop_token st, int i) { |
60 | assert(st.stop_possible()); |
61 | assert(!st.stop_requested()); |
62 | result += i; |
63 | }, |
64 | 5}; |
65 | jt.join(); |
66 | assert(result == 5); |
67 | } |
68 | |
69 | // otherwise |
70 | // invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...) |
71 | { |
72 | int result = 0; |
73 | std::jthread jt{[&result](int i) { result += i; }, 5}; |
74 | jt.join(); |
75 | assert(result == 5); |
76 | } |
77 | |
78 | // with the values produced by auto being materialized ([conv.rval]) in the constructing thread. |
79 | { |
80 | struct TrackThread { |
81 | std::jthread::id threadId; |
82 | bool copyConstructed = false; |
83 | bool moveConstructed = false; |
84 | |
85 | TrackThread() : threadId(std::this_thread::get_id()) {} |
86 | TrackThread(const TrackThread&) : threadId(std::this_thread::get_id()), copyConstructed(true) {} |
87 | TrackThread(TrackThread&&) : threadId(std::this_thread::get_id()), moveConstructed(true) {} |
88 | }; |
89 | |
90 | auto mainThread = std::this_thread::get_id(); |
91 | |
92 | TrackThread arg1; |
93 | std::jthread jt1{[mainThread](const TrackThread& arg) { |
94 | assert(arg.threadId == mainThread); |
95 | assert(arg.threadId != std::this_thread::get_id()); |
96 | assert(arg.copyConstructed); |
97 | }, |
98 | arg1}; |
99 | |
100 | TrackThread arg2; |
101 | std::jthread jt2{[mainThread](const TrackThread& arg) { |
102 | assert(arg.threadId == mainThread); |
103 | assert(arg.threadId != std::this_thread::get_id()); |
104 | assert(arg.moveConstructed); |
105 | }, |
106 | std::move(arg2)}; |
107 | } |
108 | |
109 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
110 | // [Note 1: This implies that any exceptions not thrown from the invocation of the copy |
111 | // of f will be thrown in the constructing thread, not the new thread. - end note] |
112 | { |
113 | struct Exception { |
114 | std::jthread::id threadId; |
115 | }; |
116 | struct ThrowOnCopyFunc { |
117 | ThrowOnCopyFunc() = default; |
118 | ThrowOnCopyFunc(const ThrowOnCopyFunc&) { throw Exception{.threadId: std::this_thread::get_id()}; } |
119 | void operator()() const {} |
120 | }; |
121 | ThrowOnCopyFunc f1; |
122 | try { |
123 | std::jthread jt{f1}; |
124 | assert(false); |
125 | } catch (const Exception& e) { |
126 | assert(e.threadId == std::this_thread::get_id()); |
127 | } |
128 | } |
129 | #endif // !defined(TEST_HAS_NO_EXCEPTIONS) |
130 | |
131 | // Synchronization: The completion of the invocation of the constructor |
132 | // synchronizes with the beginning of the invocation of the copy of f. |
133 | { |
134 | int flag = 0; |
135 | struct Arg { |
136 | int& flag_; |
137 | Arg(int& f) : flag_(f) {} |
138 | |
139 | Arg(const Arg& other) : flag_(other.flag_) { flag_ = 5; } |
140 | }; |
141 | |
142 | Arg arg(flag); |
143 | std::jthread jt( |
144 | [&flag](const auto&) { |
145 | assert(flag == 5); // happens-after the copy-construction of arg |
146 | }, |
147 | arg); |
148 | } |
149 | |
150 | // Per https://eel.is/c++draft/thread.jthread.class#thread.jthread.cons-8: |
151 | // |
152 | // Throws: system_error if unable to start the new thread. |
153 | // Error conditions: |
154 | // resource_unavailable_try_again - the system lacked the necessary resources to create another thread, |
155 | // or the system-imposed limit on the number of threads in a process would be exceeded. |
156 | // |
157 | // Unfortunately, this is extremely hard to test portably so we don't have a test for this error condition right now. |
158 | |
159 | return 0; |
160 | } |
161 | |