• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jbaldwin / liblifthttp / 9bcc3dee63a05adea135e76f8367d462ad69234c-PR-163

02 May 2025 01:00PM UTC coverage: 88.027%. First build
9bcc3dee63a05adea135e76f8367d462ad69234c-PR-163

Pull #163

github

web-flow
Merge b1a52e2d4 into ba15ed4fd
Pull Request #163: added network (CURLOPT_ERRORBUFFER) error message to response

12 of 16 new or added lines in 3 files covered. (75.0%)

1169 of 1328 relevant lines covered (88.03%)

15589.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.91
/src/client.cpp
1
#include "lift/client.hpp"
2
#include "lift/init.hpp"
3

4
#include <curl/curl.h>
5
#include <curl/multi.h>
6

7
#include <chrono>
8
#include <sys/syscall.h>
9
#include <thread>
10
#include <unistd.h>
11

12
using namespace std::chrono_literals;
13

14
namespace lift
15
{
16
template<typename output_type, typename input_type>
17
static auto uv_type_cast(input_type* i) -> output_type*
19,317✔
18
{
19
    auto* void_ptr = static_cast<void*>(i);
19,317✔
20
    return static_cast<output_type*>(void_ptr);
19,317✔
21
}
22

23
class curl_context
24
{
25
public:
26
    explicit curl_context(client& c) : m_client(c) { m_poll_handle.data = this; }
18,529✔
27

28
    ~curl_context() = default;
29

30
    curl_context(const curl_context&)                             = delete;
31
    curl_context(curl_context&&)                                  = delete;
32
    auto operator=(const curl_context&) noexcept -> curl_context& = delete;
33
    auto operator=(curl_context&&) noexcept -> curl_context&      = delete;
34

35
    auto init(uv_loop_t* uv_loop, curl_socket_t sock_fd) -> void
19,316✔
36
    {
37
        m_sock_fd = sock_fd;
19,316✔
38
        uv_poll_init(uv_loop, &m_poll_handle, m_sock_fd);
19,316✔
39
    }
19,314✔
40

41
    auto close()
19,318✔
42
    {
43
        uv_poll_stop(&m_poll_handle);
19,318✔
44
        /**
45
         * uv requires us to jump through a few hoops before we can delete ourselves.
46
         */
47
        uv_close(uv_type_cast<uv_handle_t>(&m_poll_handle), curl_context::on_close);
19,318✔
48
    }
19,319✔
49

50
    inline auto lift_client() -> client& { return m_client; }
24,426✔
51
    inline auto uv_poll_handle() -> uv_poll_t& { return m_poll_handle; }
19,310✔
52
    inline auto curl_sock_fd() -> curl_socket_t { return m_sock_fd; }
5,109✔
53

54
    static auto on_close(uv_handle_t* handle) -> void
19,319✔
55
    {
56
        auto* cc = static_cast<curl_context*>(handle->data);
19,319✔
57
        /**
58
         * uv has signaled that it is finished with the m_poll_handle,
59
         * we can now safely tell the event loop to re-use this curl context.
60
         */
61
        cc->lift_client().m_curl_context_ready.emplace_back(cc);
19,319✔
62
    }
19,319✔
63

64
private:
65
    client&       m_client;
66
    uv_poll_t     m_poll_handle{};
67
    curl_socket_t m_sock_fd{CURL_SOCKET_BAD};
68
};
69

70
auto curl_start_timeout(CURLM* cmh, long timeout_ms, void* user_data) -> void;
71

72
auto curl_handle_socket_actions(CURL* curl, curl_socket_t socket, int action, void* user_data, void* socketp) -> int;
73

74
auto uv_close_callback(uv_handle_t* handle) -> void;
75

76
auto on_uv_timeout_callback(uv_timer_t* handle) -> void;
77

78
auto on_uv_curl_perform_callback(uv_poll_t* req, int status, int events) -> void;
79

80
auto on_uv_requests_accept_async(uv_async_t* handle) -> void;
81

82
auto on_uv_shutdown_async(uv_async_t* handle) -> void;
83

84
auto on_uv_timesup_callback(uv_timer_t* handle) -> void;
85

86
client::client(options opts)
17✔
87
    : m_connect_timeout(std::move(opts.connect_timeout)),
17✔
88
      m_curl_context_ready(),
89
      m_resolve_hosts(std::move(opts.resolve_hosts).value_or(std::vector<resolve_host>{})),
34✔
90
      m_share_ptr(std::move(opts.share)),
17✔
91
      m_on_thread_callback(std::move(opts.on_thread_callback))
51✔
92
{
93
    global_init();
17✔
94

95
    for (std::size_t i = 0; i < opts.reserve_connections.value_or(0); ++i)
17✔
96
    {
97
        m_executors.push_back(executor::make_unique(this));
×
98
    }
99

100
    uv_loop_init(&m_uv_loop);
17✔
101

102
    uv_async_init(&m_uv_loop, &m_uv_async, on_uv_requests_accept_async);
17✔
103
    m_uv_async.data = this;
17✔
104

105
    uv_async_init(&m_uv_loop, &m_uv_async_shutdown_pipe, on_uv_shutdown_async);
17✔
106
    m_uv_async_shutdown_pipe.data = this;
17✔
107

108
    uv_timer_init(&m_uv_loop, &m_uv_timer_curl);
17✔
109
    m_uv_timer_curl.data = this;
17✔
110

111
    uv_timer_init(&m_uv_loop, &m_uv_timer_timeout);
17✔
112
    m_uv_timer_timeout.data = this;
17✔
113

114
    curl_multi_setopt(m_cmh, CURLMOPT_SOCKETFUNCTION, curl_handle_socket_actions);
17✔
115
    curl_multi_setopt(m_cmh, CURLMOPT_SOCKETDATA, this);
17✔
116
    curl_multi_setopt(m_cmh, CURLMOPT_TIMERFUNCTION, curl_start_timeout);
17✔
117
    curl_multi_setopt(m_cmh, CURLMOPT_TIMERDATA, this);
17✔
118

119
    if (opts.max_connections.has_value())
17✔
120
    {
121
        curl_multi_setopt(m_cmh, CURLMOPT_MAXCONNECTS, static_cast<long>(opts.max_connections.value()));
×
122
    }
123

124
    m_background_thread = std::thread{[this] { run(); }};
34✔
125

126
    /**
127
     * 'Spin' wait for the thread to spin-up and run the event loop,
128
     * this means when the constructor returns the user can start adding requests
129
     * immediately without waiting.
130
     */
131
    while (!is_running())
134✔
132
    {
133
        std::this_thread::yield();
117✔
134
    }
135
}
17✔
136

137
client::~client()
17✔
138
{
139
    m_is_stopping.exchange(true, std::memory_order_release);
17✔
140

141
    // Block until all requests are completed.
142
    while (!empty())
61✔
143
    {
144
        std::this_thread::sleep_for(1ms);
44✔
145
    }
146

147
    // This breaks the main UV_RUN_DEFAULT loop.
148
    uv_stop(&m_uv_loop);
17✔
149
    // This tells the loop to cleanup all its resources.
150
    uv_async_send(&m_uv_async_shutdown_pipe);
17✔
151

152
    // Wait for the loop to finish cleaning up before closing up shop.
153
    while (uv_loop_alive(&m_uv_loop) > 0)
34✔
154
    {
155
        std::this_thread::sleep_for(1ms);
17✔
156
    }
157

158
    uv_loop_close(&m_uv_loop);
17✔
159

160
    m_background_thread.join();
17✔
161
    m_executors.clear();
17✔
162

163
    curl_multi_cleanup(m_cmh);
17✔
164
    global_cleanup();
17✔
165
}
17✔
166

167
auto client::start_request(request_ptr&& request_ptr) -> request::async_future_type
2✔
168
{
169
    if (request_ptr == nullptr)
2✔
170
    {
171
        throw std::runtime_error{"lift::client::start_request (future) The request_ptr cannot be nullptr."};
×
172
    }
173

174
    auto future = request_ptr->async_future();
2✔
175
    start_request_common(std::move(request_ptr));
2✔
176
    return future;
2✔
177
}
178

179
auto client::start_request(request_ptr&& request_ptr, request::async_callback_type callback) -> void
20,072✔
180
{
181
    if (request_ptr == nullptr)
20,072✔
182
    {
183
        throw std::runtime_error{"lift::client::start_request (callback) The request_ptr cannot be nullptr."};
1✔
184
    }
185
    if (callback == nullptr)
20,076✔
186
    {
187
        throw std::runtime_error{"lift::client::start_request (callback) The callback cannot be nullptr."};
1✔
188
    }
189

190
    request_ptr->async_callback(std::move(callback));
20,068✔
191
    start_request_common(std::move(request_ptr));
20,085✔
192
}
20,078✔
193

194
auto client::start_request_common(request_ptr&& request_ptr) -> void
20,087✔
195
{
196
    if (m_is_stopping.load(std::memory_order_acquire))
20,087✔
197
    {
198
        start_request_notify_failed_start(std::move(request_ptr));
×
199
        return;
1✔
200
    }
201

202
    // Do this now so that the event loop takes into account 'pending' requests as well.
203
    m_active_request_count.fetch_add(1, std::memory_order_release);
20,098✔
204

205
    {
206
        std::lock_guard<std::mutex> guard{m_pending_requests_lock};
40,145✔
207
        m_pending_requests.emplace_back(std::move(request_ptr));
20,098✔
208
    }
209
    uv_async_send(&m_uv_async);
20,099✔
210
}
211

212
auto client::run() -> void
17✔
213
{
214
    if (m_on_thread_callback != nullptr)
17✔
215
    {
216
        m_on_thread_callback();
×
217
    }
218

219
    m_is_running.exchange(true, std::memory_order_release);
17✔
220
    if (uv_run(&m_uv_loop, UV_RUN_DEFAULT) > 0)
17✔
221
    {
222
        // Run until all uv_handle_t objects are cleaned up.
223
        while (uv_run(&m_uv_loop, UV_RUN_NOWAIT) > 0)
2✔
224
        {
225
            std::this_thread::sleep_for(1ms);
×
226
        }
227
    }
228

229
    m_is_running.exchange(false, std::memory_order_release);
17✔
230

231
    if (m_on_thread_callback != nullptr)
17✔
232
    {
233
        m_on_thread_callback();
×
234
    }
235
}
17✔
236

237
auto client::check_actions() -> void
47,580✔
238
{
239
    check_actions(CURL_SOCKET_TIMEOUT, 0);
47,580✔
240
}
47,579✔
241

242
auto client::check_actions(curl_socket_t socket, int event_bitmask) -> void
52,688✔
243
{
244
    int       running_handles = 0;
52,688✔
245
    CURLMcode curl_code       = CURLM_OK;
52,688✔
246
    do
×
247
    {
248
        curl_code = curl_multi_socket_action(m_cmh, socket, event_bitmask, &running_handles);
52,688✔
249
    } while (curl_code == CURLM_CALL_MULTI_PERFORM);
52,686✔
250

251
    CURLMsg* message   = nullptr;
52,686✔
252
    int      msgs_left = 0;
52,686✔
253

254
    while ((message = curl_multi_info_read(m_cmh, &msgs_left)) != nullptr)
72,895✔
255
    {
256
        if (message->msg == CURLMSG_DONE)
20,212✔
257
        {
258
            CURL*    easy_handle = message->easy_handle;
20,213✔
259
            CURLcode easy_result = message->data.result;
20,213✔
260

261
            executor* exe = nullptr;
20,213✔
262
            curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &exe);
20,213✔
263
            executor_ptr executor_ptr{exe};
20,212✔
264

265
            // Remove the handle from curl multi since it is done processing.
266
            curl_multi_remove_handle(m_cmh, easy_handle);
20,208✔
267

268
            // Notify the user (if it hasn't already timed out) that the request is completed.
269
            // This will also return the executor to the pool for reuse.
270
            complete_request_normal(std::move(executor_ptr), easy_result);
20,214✔
271
        }
272
    }
273
}
52,676✔
274

275
auto client::complete_request_normal(executor_ptr exe_ptr, CURLcode curl_code) -> void
20,212✔
276
{
277
    auto& exe = *exe_ptr.get();
20,212✔
278

279
    if (exe.m_on_complete_handler_processed == false)
20,211✔
280
    {
281
        // Don't run this logic twice ever.
282
        exe.m_on_complete_handler_processed = true;
20,211✔
283
        // Since the request completed remove it from the timeout set if it is there.
284
        remove_timeout(exe);
20,211✔
285

286
        // Ownership over the async_handlers_type must be 'owned' here, otherwise when the request
287
        // is moved below it will move out from under us and cause a segfault due to the custom
288
        // 'copy_but_actually_move' object wrapper.
289
        auto on_complete_handler = std::move(exe.m_request_async->m_on_complete_handler.m_object).value();
40,420✔
290

291
        if (std::holds_alternative<request::async_callback_type>(on_complete_handler))
20,210✔
292
        {
293
            exe.copy_curl_to_response(curl_code);
20,209✔
294

295
            auto& callback = std::get<request::async_callback_type>(on_complete_handler);
20,206✔
296
            callback(std::move(exe.m_request_async), std::move(exe.m_response));
20,206✔
297
        }
298
        else if (std::holds_alternative<request::async_promise_type>(on_complete_handler))
3✔
299
        {
300
            exe.copy_curl_to_response(curl_code);
3✔
301

302
            auto& promise = std::get<request::async_promise_type>(on_complete_handler);
3✔
303
            promise.set_value(std::make_pair(std::move(exe.m_request_async), std::move(exe.m_response)));
3✔
304
        }
305
        // else do nothing for std::monostate, the user doesn't want to be notified or this request
306
        // has timedout but was allowed to finish establishing a connection.
307
    }
308

309
    return_executor(std::move(exe_ptr));
20,205✔
310
    m_active_request_count.fetch_sub(1, std::memory_order_release);
20,210✔
311
}
20,210✔
312

313
auto client::complete_request_timeout(executor& exe) -> void
1✔
314
{
315
    /**
316
     * NOTE: This function doesn't remove the request from the curl multi handle nor does it
317
     *       return the executor to the pool.  That is because this function is still allowing
318
     *       for curl to complete to establish http connections for extremely *low* timeout values.
319
     *
320
     *       This function expects that complete_request_normal will eventually be called by curl
321
     *       to properly cleanup and finish the request and allow for the http connection to fully
322
     *       establish.
323
     */
324

325
    // Call on complete if it exists and hasn't been called before.
326
    if (exe.m_on_complete_handler_processed == false)
1✔
327
    {
328
        // Don't run this logic twice ever.
329
        exe.m_on_complete_handler_processed = true;
1✔
330

331
        // Ownership over the async_handlers_type must be 'owned' here, otherwise when the request
332
        // is moved below it will move out from under us and cause a segfault due to the custom
333
        // 'copy_but_actually_move' object wrapper.
334
        auto on_complete_handler = std::move(exe.m_request_async->m_on_complete_handler.m_object).value();
2✔
335

336
        // Removing the timesup is done in the uv timesup callback so it can prune
337
        // every single item that has timesup'ed.  Doing it here will cause 1 item
338
        // per loop iteration to be removed if there are multiple items with the same timesup value.
339
        // remove_timeout(exe);
340

341
        if (std::holds_alternative<request::async_callback_type>(on_complete_handler))
1✔
342
        {
343
            auto copy = complete_request_timeout_common(exe);
1✔
344

345
            auto& callback = std::get<request::async_callback_type>(on_complete_handler);
1✔
346
            callback(std::move(copy), std::move(exe.m_response));
1✔
347
        }
348
        else if (std::holds_alternative<request::async_promise_type>(on_complete_handler))
×
349
        {
350
            auto copy = complete_request_timeout_common(exe);
×
351

352
            auto& promise = std::get<request::async_promise_type>(on_complete_handler);
×
353
            promise.set_value(std::make_pair(std::move(copy), std::move(exe.m_response)));
×
354
        }
355
        // else do nothing for std::monostate, the user doesn't want to be notified.
356
    }
357
}
1✔
358

359
auto client::complete_request_timeout_common(executor& exe) -> request_ptr
1✔
360
{
361
    exe.m_response.m_lift_status = lift::lift_status::timeout;
1✔
362
    exe.set_timesup_response(exe.m_request->timeout().value());
1✔
363

364
    // IMPORTANT! Copying here is required _OR_ shared ownership must be added as libcurl
365
    // maintains char* type pointers into the request data structure.  There is no guarantee
366
    // after moving into user land that it will stay alive long enough until curl finishes its
367
    // own timeout.  Shared ownership would most likely require locks as well since any curl
368
    // handle still in the curl multi handle could be mutated by curl at any moment, copying
369
    // seems far safer.
370

371
    // IMPORTANT! The request's async completion handler object is _MOVED_ from the original
372
    // object into the copied object.  After making this copy the original object must not invoke
373
    // any async handlers (future or callback).
374
    auto copy_ptr = std::make_unique<request>(*exe.m_request_async);
1✔
375
    return copy_ptr;
1✔
376
}
377

378
auto client::add_timeout(executor& exe) -> void
20,214✔
379
{
380
    auto* request = exe.m_request;
20,214✔
381
    if (request->timeout().has_value())
20,214✔
382
    {
383
        auto timeout = exe.m_request->timeout().value();
20,214✔
384

385
        std::optional<std::chrono::milliseconds> connect_timeout{std::nullopt};
20,214✔
386
        if (request->connect_timeout().has_value())
20,214✔
387
        {
388
            // Prefer the individual connect timeout over the event loop default.
389
            connect_timeout = request->connect_timeout().value();
×
390
        }
391
        else if (m_connect_timeout.has_value())
20,214✔
392
        {
393
            connect_timeout = m_connect_timeout;
1✔
394
        }
395

396
        if (connect_timeout.has_value())
20,213✔
397
        {
398
            if (connect_timeout.value() > timeout)
1✔
399
            {
400
                auto       now         = uv_now(&m_uv_loop);
1✔
401
                time_point tp          = now + static_cast<time_point>(timeout.count());
1✔
402
                exe.m_timeout_iterator = m_timeouts.emplace(tp, &exe);
1✔
403

404
                update_timeouts();
1✔
405

406
                curl_easy_setopt(
1✔
407
                    exe.m_curl_handle, CURLOPT_TIMEOUT_MS, static_cast<long>(connect_timeout.value().count()));
408
            }
409
            else
410
            {
411
                // If the user set a longer timeout on the individual request, just let curl handle it.
412
                curl_easy_setopt(exe.m_curl_handle, CURLOPT_TIMEOUT_MS, static_cast<long>(timeout.count()));
×
413
            }
414
        }
415
        else
416
        {
417
            curl_easy_setopt(exe.m_curl_handle, CURLOPT_TIMEOUT_MS, static_cast<long>(timeout.count()));
20,213✔
418
        }
419
    }
420
}
20,214✔
421

422
auto client::remove_timeout(executor& exe) -> std::multimap<uint64_t, executor*>::iterator
20,213✔
423
{
424
    if (exe.m_timeout_iterator.has_value())
20,213✔
425
    {
426
        auto iter = exe.m_timeout_iterator.value();
1✔
427
        auto next = m_timeouts.erase(iter);
1✔
428
        exe.m_timeout_iterator.reset();
1✔
429

430
        // Anytime an item is removed the timesup timer might need to be adjusted.
431
        update_timeouts();
1✔
432

433
        return next;
1✔
434
    }
435
    else
436
    {
437
        // This is probably a logic error if the passed in executor doesn't
438
        // have a timeout, we'll update the timeouts to be sture we are triggering
439
        // at the correct next timeout and return the end() so as not to go into
440
        // an infinite loop if this is the first item.
441
        update_timeouts();
20,211✔
442
        return m_timeouts.end();
20,210✔
443
    }
444
}
445

446
auto client::update_timeouts() -> void
20,213✔
447
{
448
    // TODO only change if it needs to change, this will probably require
449
    // an iterator to the item just added or removed to properly skip
450
    // setting the timer every single time.
451

452
    if (!m_timeouts.empty())
20,213✔
453
    {
454
        auto now   = uv_now(&m_uv_loop);
1✔
455
        auto first = m_timeouts.begin()->first;
1✔
456

457
        // If the first item is already 'expired' setting the timer to zero
458
        // will trigger uv to call its callback on the next loop iteration.
459
        // Otherwise set the difference to how many milliseconds are between
460
        // first and now.
461
        uint64_t timer_value{0};
1✔
462
        if (first > now)
1✔
463
        {
464
            timer_value = first - now;
1✔
465
        }
466

467
        uv_timer_stop(&m_uv_timer_timeout);
1✔
468
        uv_timer_start(&m_uv_timer_timeout, on_uv_timesup_callback, timer_value, 0);
1✔
469
    }
470
    else
471
    {
472
        uv_timer_stop(&m_uv_timer_timeout);
20,212✔
473
    }
474
}
20,213✔
475

476
auto client::acquire_executor() -> std::unique_ptr<executor>
20,210✔
477
{
478
    std::unique_ptr<executor> executor_ptr{nullptr};
20,210✔
479

480
    if (!m_executors.empty())
20,210✔
481
    {
482
        executor_ptr = std::move(m_executors.back());
12,505✔
483
        m_executors.pop_back();
12,505✔
484
    }
485

486
    if (executor_ptr == nullptr)
20,209✔
487
    {
488
        executor_ptr = executor::make_unique(this);
7,706✔
489
    }
490

491
    return executor_ptr;
20,213✔
492
}
493

494
auto client::return_executor(std::unique_ptr<executor> executor_ptr) -> void
20,211✔
495
{
496
    executor_ptr->reset();
20,211✔
497
    m_executors.push_back(std::move(executor_ptr));
20,214✔
498
}
20,211✔
499

500
auto curl_start_timeout(CURLM* /*cmh*/, long timeout_ms, void* user_data) -> void
71,823✔
501
{
502
    auto* c = static_cast<client*>(user_data);
71,823✔
503

504
    // Stop the current timer regardless.
505
    uv_timer_stop(&c->m_uv_timer_curl);
71,823✔
506

507
    if (timeout_ms > 0)
71,819✔
508
    {
509
        uv_timer_start(&c->m_uv_timer_curl, on_uv_timeout_callback, static_cast<uint64_t>(timeout_ms), 0);
44,452✔
510
    }
511
    else if (timeout_ms == 0)
27,367✔
512
    {
513
        c->check_actions();
27,357✔
514
    }
515
}
71,815✔
516

517
auto curl_handle_socket_actions(CURL* /*curl*/, curl_socket_t socket, int action, void* user_data, void* socketp) -> int
38,637✔
518
{
519
    auto* c = static_cast<client*>(user_data);
38,637✔
520

521
    curl_context* cc = nullptr;
38,637✔
522
    if (action == CURL_POLL_IN || action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
38,637✔
523
    {
524
        if (socketp != nullptr)
19,319✔
525
        {
526
            // existing request
527
            cc = static_cast<curl_context*>(socketp);
1✔
528
        }
529
        else
530
        {
531
            // new request, and no curl context's available? make one
532
            if (c->m_curl_context_ready.empty())
19,318✔
533
            {
534
                auto curl_context_ptr = std::make_unique<curl_context>(*c);
37,050✔
535
                cc                    = curl_context_ptr.release();
18,526✔
536
            }
537
            else
538
            {
539
                cc = c->m_curl_context_ready.front().release();
789✔
540
                c->m_curl_context_ready.pop_front();
789✔
541
            }
542

543
            cc->init(&c->m_uv_loop, socket);
19,316✔
544
            curl_multi_assign(c->m_cmh, socket, static_cast<void*>(cc));
19,315✔
545
        }
546
    }
547

548
    switch (action)
38,634✔
549
    {
550
        case CURL_POLL_IN:
19,314✔
551
            uv_poll_start(&cc->uv_poll_handle(), UV_READABLE, on_uv_curl_perform_callback);
19,314✔
552
            break;
19,318✔
553
        case CURL_POLL_OUT:
1✔
554
            uv_poll_start(&cc->uv_poll_handle(), UV_WRITABLE, on_uv_curl_perform_callback);
1✔
555
            break;
1✔
556
        case CURL_POLL_INOUT:
×
557
            uv_poll_start(&cc->uv_poll_handle(), UV_READABLE | UV_WRITABLE, on_uv_curl_perform_callback);
×
558
            break;
1✔
559
        case CURL_POLL_REMOVE:
19,319✔
560
            if (socketp != nullptr)
19,319✔
561
            {
562
                cc = static_cast<curl_context*>(socketp);
19,319✔
563
                cc->close(); // signal this handle is done
19,319✔
564
                curl_multi_assign(c->m_cmh, socket, nullptr);
19,318✔
565
            }
566
            break;
19,318✔
567
        default:
×
568
            break;
×
569
    }
570

571
    return 0;
38,638✔
572
}
573

574
auto uv_close_callback(uv_handle_t* /*handle*/) -> void
68✔
575
{
576
    // auto* c = static_cast<client*>(handle->data);
577
    // (void)c;
578

579
    // Currently nothing needs to be done since all handles the event loop uses
580
    // are allocated within the lift::client object and not separate on the heap.
581
}
68✔
582

583
auto on_uv_timeout_callback(uv_timer_t* handle) -> void
10✔
584
{
585
    auto* c = static_cast<client*>(handle->data);
10✔
586
    c->check_actions();
10✔
587
}
10✔
588

589
auto on_uv_curl_perform_callback(uv_poll_t* req, int status, int events) -> void
5,109✔
590
{
591
    auto* cc = static_cast<curl_context*>(req->data);
5,109✔
592
    auto& c  = cc->lift_client();
5,109✔
593

594
    int32_t action = 0;
5,109✔
595
    if (status < 0)
5,109✔
596
    {
597
        action = CURL_CSELECT_ERR;
×
598
    }
599
    if (status == 0)
5,109✔
600
    {
601
        if ((events & UV_READABLE) != 0)
5,109✔
602
        {
603
            action |= CURL_CSELECT_IN;
5,108✔
604
        }
605
        if ((events & UV_WRITABLE) != 0)
5,109✔
606
        {
607
            action |= CURL_CSELECT_OUT;
1✔
608
        }
609
    }
610

611
    c.check_actions(cc->curl_sock_fd(), action);
5,109✔
612
}
5,109✔
613

614
auto on_uv_requests_accept_async(uv_async_t* handle) -> void
23✔
615
{
616
    auto* c = static_cast<client*>(handle->data);
23✔
617

618
    /**
619
     * This lock must not have any "curl_*" functions called
620
     * while it is held, curl has its own internal locks and
621
     * it can cause a deadlock.  This means we intentionally swap
622
     * vectors before working on them so we have exclusive access
623
     * to the request objects on the client thread.
624
     */
625
    {
626
        std::lock_guard<std::mutex> guard{c->m_pending_requests_lock};
46✔
627
        // swap so we can release the lock as quickly as possible
628
        c->m_grabbed_requests.swap(c->m_pending_requests);
23✔
629
    }
630

631
    for (auto& request_ptr : c->m_grabbed_requests)
20,235✔
632
    {
633
        auto executor_ptr = c->acquire_executor();
40,423✔
634
        executor_ptr->start_async(std::move(request_ptr), c->m_share_ptr.get());
20,213✔
635
        executor_ptr->prepare();
20,211✔
636

637
        // This must be done before adding to the CURLM* object,
638
        // if not its possible a very fast request could complete
639
        // before this gets into the multi-map!
640
        c->add_timeout(*executor_ptr);
20,214✔
641

642
        auto curl_code = curl_multi_add_handle(c->m_cmh, executor_ptr->m_curl_handle);
20,212✔
643

644
        if (curl_code != CURLM_OK && curl_code != CURLM_CALL_MULTI_PERFORM)
20,213✔
645
        {
646
            /**
647
             * If curl_multi_add_handle fails then notify the user that the request failed to start
648
             * immediately.  This will return the just acquired executor back into the pool.
649
             */
NEW
650
            c->complete_request_normal(std::move(executor_ptr), CURLcode::CURLE_SEND_ERROR);
×
651
        }
652
        else
653
        {
654
            /**
655
             * Drop the unique_ptr safety around the request_ptr while it is being
656
             * processed by curl.  When curl is finished completing the request
657
             * it will be put back into a request object for the client to use.
658
             */
659
            (void)executor_ptr.release();
20,213✔
660

661
            /**
662
             * Immediately call curl's check action to get the current request moving.
663
             * Curl appears to have an internal queue and if it gets too long it might
664
             * drop requests.
665
             */
666
            c->check_actions();
20,212✔
667
        }
668
    }
669

670
    c->m_grabbed_requests.clear();
25✔
671
}
23✔
672

673
auto on_uv_shutdown_async(uv_async_t* handle) -> void
17✔
674
{
675
    auto* c = static_cast<client*>(handle->data);
17✔
676

677
    uv_timer_stop(&c->m_uv_timer_curl);
17✔
678
    uv_timer_stop(&c->m_uv_timer_timeout);
17✔
679
    uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_timer_curl), uv_close_callback);
17✔
680
    uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_timer_timeout), uv_close_callback);
17✔
681
    uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_async), uv_close_callback);
17✔
682
    uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_async_shutdown_pipe), uv_close_callback);
17✔
683
}
17✔
684

685
auto on_uv_timesup_callback(uv_timer_t* handle) -> void
1✔
686
{
687
    auto* c       = static_cast<client*>(handle->data);
1✔
688
    auto& timesup = c->m_timeouts;
1✔
689

690
    if (timesup.empty())
1✔
691
    {
692
        return;
×
693
    }
694

695
    auto now = uv_now(&c->m_uv_loop);
1✔
696

697
    // While the items in the timesup map are <= now "timesup" them to the client.
698
    auto iter = timesup.begin();
1✔
699
    while (iter != timesup.end())
2✔
700
    {
701
        auto& [tp, exe] = *iter;
1✔
702
        if (tp > now)
1✔
703
        {
704
            // Everything past this point has more time to wait.
705
            break;
×
706
        }
707

708
        c->complete_request_timeout(*exe);
1✔
709
        iter = c->remove_timeout(*exe);
1✔
710
    }
711
}
712

713
} // namespace lift
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc