File: dtor.pass.cpp

package info (click to toggle)
llvm-toolchain-19 1%3A19.1.7-3~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 1,998,492 kB
  • sloc: cpp: 6,951,680; ansic: 1,486,157; asm: 913,598; python: 232,024; f90: 80,126; objc: 75,281; lisp: 37,276; pascal: 16,990; sh: 10,009; ml: 5,058; perl: 4,724; awk: 3,523; makefile: 3,167; javascript: 2,504; xml: 892; fortran: 664; cs: 573
file content (169 lines) | stat: -rw-r--r-- 4,929 bytes parent folder | download | duplicates (7)
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
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// UNSUPPORTED: no-threads
// UNSUPPORTED: libcpp-has-no-experimental-stop_token
// UNSUPPORTED: c++03, c++11, c++14, c++17
// XFAIL: availability-synchronization_library-missing

// ~stop_callback();

#include <atomic>
#include <cassert>
#include <chrono>
#include <functional>
#include <optional>
#include <stop_token>
#include <type_traits>
#include <utility>
#include <vector>

#include "make_test_thread.h"
#include "test_macros.h"

struct CallbackHolder;

struct DeleteHolder {
  CallbackHolder& holder_;
  void operator()() const;
};

struct CallbackHolder {
  std::unique_ptr<std::stop_callback<DeleteHolder>> callback_;
};

void DeleteHolder::operator()() const { holder_.callback_.reset(); }

int main(int, char**) {
  // Unregisters the callback from the owned stop state, if any
  {
    std::stop_source ss;
    bool called = false;

    {
      std::stop_callback sc(ss.get_token(), [&] { called = true; });
    }
    ss.request_stop();
    assert(!called);
  }

  // The destructor does not block waiting for the execution of another
  // callback registered by an associated stop_callback.
  {
    std::stop_source ss;

    std::atomic<int> startedIndex    = 0;
    std::atomic<bool> callbackFinish = false;

    std::optional<std::stop_callback<std::function<void()>>> sc1(std::in_place, ss.get_token(), [&] {
      startedIndex = 1;
      startedIndex.notify_all();
      callbackFinish.wait(false);
    });

    std::optional<std::stop_callback<std::function<void()>>> sc2(std::in_place, ss.get_token(), [&] {
      startedIndex = 2;
      startedIndex.notify_all();
      callbackFinish.wait(false);
    });

    auto thread = support::make_test_thread([&] { ss.request_stop(); });

    startedIndex.wait(0);

    // now one of the callback has started but not finished.
    if (startedIndex == 1) {
      sc2.reset();   // destructor should not block
    } else if (startedIndex == 2) {
      sc1.reset();   // destructor should not block
    } else {
      assert(false); // something is wrong
    }

    callbackFinish = true;
    callbackFinish.notify_all();
    thread.join();
  }

  // If callback is concurrently executing on another thread, then the
  // return from the invocation of callback strongly happens before ([intro.races])
  // callback is destroyed.
  {
    struct Callback {
      std::atomic<bool>& started_;
      std::atomic<bool>& waitDone_;
      std::atomic<bool>& finished_;
      bool moved = false;

      Callback(std::atomic<bool>& started, std::atomic<bool>& waitDone, std::atomic<bool>& finished)
          : started_(started), waitDone_(waitDone), finished_(finished) {}
      Callback(Callback&& other) : started_(other.started_), waitDone_(other.waitDone_), finished_(other.finished_) {
        other.moved = true;
      }

      void operator()() const {
        struct ScopedGuard {
          std::atomic<bool>& g_finished_;
          ~ScopedGuard() { g_finished_.store(true, std::memory_order_relaxed); }
        };

        started_ = true;
        started_.notify_all();
        waitDone_.wait(false);
        ScopedGuard g{finished_};
      }

      ~Callback() {
        if (!moved) {
          // destructor has to be called after operator() returns
          assert(finished_.load(std::memory_order_relaxed));
        }
      }
    };

    std::stop_source ss;

    std::atomic<bool> started  = false;
    std::atomic<bool> waitDone = false;
    std::atomic<bool> finished = false;

    std::optional<std::stop_callback<Callback>> sc{
        std::in_place, ss.get_token(), Callback{started, waitDone, finished}};

    auto thread1 = support::make_test_thread([&] { ss.request_stop(); });
    started.wait(false);

    auto thread2 = support::make_test_thread([&] {
      using namespace std::chrono_literals;
      std::this_thread::sleep_for(1ms);
      waitDone = true;
      waitDone.notify_all();
    });

    sc.reset(); // destructor should block until operator() returns, i.e. waitDone to be true

    thread1.join();
    thread2.join();
  }

  // If callback is executing on the current thread, then the destructor does not block ([defns.block])
  // waiting for the return from the invocation of callback.
  {
    std::stop_source ss;

    CallbackHolder holder;
    holder.callback_ = std::make_unique<std::stop_callback<DeleteHolder>>(ss.get_token(), DeleteHolder{holder});

    assert(holder.callback_ != nullptr);

    ss.request_stop(); // the callbacks deletes itself. if the destructor blocks, it would be deadlock
    assert(holder.callback_ == nullptr);
  }

  return 0;
}