Coverage Report

Created: 2026-10-28 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/resolver_cancel_gate.h
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief Bounded rendezvous for the resolver queued-cancellation scenario.
9
10
#ifndef PROTO_FUZZER_RESOLVER_CANCEL_GATE_H_
11
#define PROTO_FUZZER_RESOLVER_CANCEL_GATE_H_
12
13
#include <chrono>
14
#include <condition_variable>
15
#include <mutex>
16
17
namespace proto_fuzzer {
18
19
namespace resolver_cancel_detail {
20
21
/// Shared state for the single opt-in resolver cancellation rendezvous.
22
struct GateState {
23
  /// Serializes every state transition and condition wait.
24
  std::mutex mutex;
25
  /// Signals both lookup arrival and scope-owned release.
26
  std::condition_variable released;
27
  /// Whether the current scope should hold the first lookup.
28
  bool armed = false;
29
  /// Whether the resolver worker has reached the gate.
30
  bool first_lookup_waiting = false;
31
};
32
33
/// Access the process-wide resolver cancellation rendezvous.
34
/// @return Shared gate state.
35
840
inline GateState& State() {
36
840
  static GateState state;
37
840
  return state;
38
840
}
39
40
}  // namespace resolver_cancel_detail
41
42
/// Hold the first opt-in getaddrinfo call until the public multi owner has
43
/// queued the cancellation handle's lookups. The owning thread releases this
44
/// gate before removing the blocked handle or cleaning up the multi. Removing
45
/// the later queued handle does not join the resolver pool, so it is safe and
46
/// intentional while the gate is held.
47
class ScopedResolverCancelGate {
48
 public:
49
175
  ScopedResolverCancelGate() {
50
175
    auto& state = resolver_cancel_detail::State();
51
175
    std::lock_guard<std::mutex> lock(state.mutex);
52
175
    state.first_lookup_waiting = false;
53
175
    state.armed = true;
54
175
  }
55
56
175
  ~ScopedResolverCancelGate() {
57
175
    auto& state = resolver_cancel_detail::State();
58
175
    {
59
175
      std::lock_guard<std::mutex> lock(state.mutex);
60
175
      state.armed = false;
61
175
    }
62
175
    state.released.notify_all();
63
175
  }
64
65
  ScopedResolverCancelGate(const ScopedResolverCancelGate&) = delete;
66
  ScopedResolverCancelGate& operator=(const ScopedResolverCancelGate&) = delete;
67
};
68
69
/// Give curl's single resolver worker a bounded opportunity to take the first
70
/// family before the main thread queues the cancellation handle.
71
/// @param timeout Maximum time to wait for the resolver worker.
72
/// @return true when the first lookup reached the gate before the deadline.
73
165
inline bool WaitForResolverCancelLookup(std::chrono::milliseconds timeout) {
74
165
  auto& state = resolver_cancel_detail::State();
75
165
  std::unique_lock<std::mutex> lock(state.mutex);
76
329
  return state.released.wait_for(lock, timeout, [&state] { return state.first_lookup_waiting; });
77
165
}
78
79
/// Block only the first lookup issued while ScopedResolverCancelGate is live.
80
/// The scope owns the release: every exit path destroys it before multi
81
/// destruction or blocker removal can join this worker. Waiting for that
82
/// explicit release prevents scheduler speed from changing the queue shape.
83
325
inline void WaitAtResolverCancelGate() {
84
325
  auto& state = resolver_cancel_detail::State();
85
325
  std::unique_lock<std::mutex> lock(state.mutex);
86
325
  if (!state.armed || state.first_lookup_waiting) {
87
160
    return;
88
160
  }
89
165
  state.first_lookup_waiting = true;
90
165
  state.released.notify_all();
91
330
  state.released.wait(lock, [&state] { return !state.armed; });
92
165
}
93
94
}  // namespace proto_fuzzer
95
96
#endif  // PROTO_FUZZER_RESOLVER_CANCEL_GATE_H_