diff --git a/src/common/src/mlib/config.h b/src/common/src/mlib/config.h index 6a1e45275b..f34266445d 100644 --- a/src/common/src/mlib/config.h +++ b/src/common/src/mlib/config.h @@ -92,8 +92,35 @@ MLIB_JUST(_mlibPickSixteenth \ MLIB_NOTHING("MSVC workaround") \ (__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, ~)) +// Expands to a single comma if invoked as a function-like macro #define _mlibCommaIfParens(...) , +/** + * @brief Expands to `1` if the given macro argument is a parenthesized group of + * tokens, otherwise `0` + */ +#define MLIB_IS_PARENTHESIZED(X) \ + _mlibHasComma(_mlibCommaIfParens X) + +/** + * @brief Pass a function-like macro name, inhibiting its expansion until the + * next pass: + * + * #define func_macro(x) x + * + * MLIB_DEFERRED(func_macro)(foo) // Expands to "func_macro(foo)", not "foo" + */ +#define MLIB_DEFERRED(MacroName) \ + /* Expand to the macro name: */ \ + MacroName \ + /*- + * Place a separator between the function macro name and whatever comes next + * in the file. Presumably, the next token will be the parens to invoke "MacroName", + * but this separator inhibits its expansion unless something else comes + * along to do another expansion pass + */ \ + MLIB_NOTHING("[separator]") + /** * A helper for isEmpty(): If given (0, 0, 0, 1), expands as: * - first: _mlibHasComma(_mlibIsEmptyCase_0001) @@ -130,9 +157,8 @@ * is not expanded and is discarded. */ #define MLIB_IF_ELSE(...) MLIB_PASTE (_mlibIfElseBranch_, MLIB_BOOLEAN (__VA_ARGS__)) -#define _mlibIfElseBranch_1(...) __VA_ARGS__ _mlibNoExpandNothing -#define _mlibIfElseBranch_0(...) MLIB_NOTHING (#__VA_ARGS__) MLIB_JUST -#define _mlibNoExpandNothing(...) MLIB_NOTHING (#__VA_ARGS__) +#define _mlibIfElseBranch_1(...) __VA_ARGS__ MLIB_NOTHING +#define _mlibIfElseBranch_0(...) MLIB_JUST /** * @brief Expands to an integer literal corresponding to the number of macro @@ -147,8 +173,8 @@ * @brief Expand to a call expression `Prefix##_argc_N(...)`, where `N` is the * number of macro arguments. */ -#define MLIB_ARGC_PICK(Prefix, ...) \ - MLIB_JUST (MLIB_PASTE_3 (Prefix, _argc_, MLIB_ARG_COUNT (__VA_ARGS__)) (__VA_ARGS__)) +#define MLIB_ARGC_PICK(Prefix, ...) MLIB_ARGC_PASTE (Prefix, __VA_ARGS__) (__VA_ARGS__) +#define MLIB_ARGC_PASTE(Prefix, ...) MLIB_PASTE_3 (Prefix, _argc_, MLIB_ARG_COUNT (__VA_ARGS__)) #ifdef __cplusplus #define mlib_is_cxx() 1 diff --git a/src/common/src/mlib/duration.h b/src/common/src/mlib/duration.h new file mode 100644 index 0000000000..6fb1e01b54 --- /dev/null +++ b/src/common/src/mlib/duration.h @@ -0,0 +1,400 @@ +/** + * @file mlib/duration.h + * @brief Duration types and functions + * @date 2025-04-17 + * + * This file contains types and functions for working with a "duration" type, + * which represents an elapsed amount of time, possibly negative. + * + * The type `mlib_duration_rep_t` is a typedef of the intregral type that is + * used to represent duration units. + * + * The `mlib_duration` is a trivial object that represents a duration of time. + * The internal representation should not be inspected outside of this file. + * + * @copyright Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MLIB_DURATION_H_INCLUDED +#define MLIB_DURATION_H_INCLUDED + +#include +#include +#include +#include + +#include +#include + +mlib_extern_c_begin (); + +/** + * @brief The integral type used to represent a count of units of time. + */ +typedef int64_t mlib_duration_rep_t; + +/** + * @brief Represents a duration of time, either positive, negative, or zero. + * + * @note A zero-initialized (static initialized) duration represents the zero + * duration (no elapsed time) + * + * @note The time representation is intended to be abstract, and should be + * converted to concrete units of time by calling the `_count` functions. + */ +typedef struct mlib_duration { + /** + * @brief The integral representation of the duration. + * + * Do not read or modify this field except to zero-initialize it. + */ + mlib_duration_rep_t _rep; +} mlib_duration; + +/** + * @brief A macro that expands to the maximum positive duration + */ +#define mlib_duration_max() (mlib_init (mlib_duration){mlib_maxof (mlib_duration_rep_t)}) +/** + * @brief A macro that expands to the minimum duration (a negative duration) + */ +#define mlib_duration_min() (mlib_init (mlib_duration){mlib_minof (mlib_duration_rep_t)}) + +/** + * @brief Obtain the count of microseconds represented by the duration (round + * toward zero) + */ +static inline mlib_duration_rep_t +mlib_microseconds_count (const mlib_duration dur) mlib_noexcept +{ + return dur._rep; +} + +/** + * @brief Obtain the count of milliseconds represented by the duration (round + * toward zero) + */ +static inline mlib_duration_rep_t +mlib_milliseconds_count (const mlib_duration dur) mlib_noexcept +{ + return mlib_microseconds_count (dur) / 1000; +} + +/** + * @brief Obtain the count of seconds represented by the duration (rounded + * toward zero) + */ +static inline mlib_duration_rep_t +mlib_seconds_count (const mlib_duration dur) mlib_noexcept +{ + return mlib_milliseconds_count (dur) / 1000; +} + +/** + * @brief Duration creation and manipulation shorthands + * + * This function-like macro is used to create and manipulate durations on-the-fly. + * It can be called with the following syntaxes: + * + * - `mlib_duration()` (no arguments) + * creates a zero-valued duration + * - `mlib_duration()` + * copies the duration object `` + * - `mlib_duration(, )` + * Creates a duration of `` instances of ``. + * - `mlib_duration(, , )` + * Manipulates a duration according to ``. + * + * In the above, `` may be a parenthesized `mlib_duration` argument list or a + * duration object; `` must be an integral expression and `` is a + * unit suffix identifer (see: `mlib_duration_with_unit`) to create a duration + * of `` instances of ``, and `` is one of: + * + * - `plus`/`minus` to add/subtract two durations. + * - `mul`/`div` to multiply/divide a duration by a scalar factor. + * - `min`/`max` to get the minimum/maximum between two durations. + * + * All duration arithmetic/conversion operations use well-defined saturating + * arithmetic, and never wrap or trap. + */ +#define mlib_duration(...) MLIB_EVAL_16 (_mlibDurationMagic (__VA_ARGS__)) +#define _mlibDurationMagic(...) \ + MLIB_DEFERRED (MLIB_ARGC_PASTE (_mlib_duration, __VA_ARGS__)) \ + (__VA_ARGS__) +// Wraps a `` argument, and expands to the magic only if it is parenthesized +#define _mlibDurationArgument(X) \ + /* If given a parenthesized expression, act as an invocation of `mlib_duration() */ \ + MLIB_IF_ELSE (MLIB_IS_PARENTHESIZED (X)) \ + /* then: */ (_mlibDurationMagic X) /* else: */ (X) + +// Wrap a macro argument that should support the duration DSL +#define mlib_duration_arg(X) MLIB_EVAL_16 (_mlibDurationArgument (X)) + +// Zero arguments, just return a zero duration: +#define _mlib_duration_argc_0() (mlib_init (mlib_duration){0}) +// One argument, just copy the duration. Passing through a function forces the type to be correct +#define _mlib_duration_argc_1(D) _mlibDurationCopy (D) +// Two arguments, the second arg is a unit suffix: +#define _mlib_duration_argc_2(Count, Unit) mlib_duration_with_unit (Count, Unit) +// Three arguments, an infix operation: +#define _mlib_duration_argc_3(Duration, Operator, Operand) \ + MLIB_DEFERRED (MLIB_PASTE (_mlibDurationInfixOperator_, Operator)) \ + (Duration, Operand) + +// By-value copy a duration +static inline mlib_duration +_mlibDurationCopy (mlib_duration d) +{ + return d; +} + +// Duration scalar multiply +#define _mlibDurationInfixOperator_mul(LHS, Fac) \ + _mlibDurationMultiply (_mlibDurationArgument (LHS), mlib_upsize_integer (Fac)) +static inline mlib_duration +_mlibDurationMultiply (const mlib_duration dur, mlib_upsized_integer fac) mlib_noexcept +{ + mlib_duration ret = {0}; + uintmax_t bits; + if ((mlib_mul) (&bits, true, true, (uintmax_t) dur._rep, fac.is_signed, fac.bits.as_unsigned)) { + if ((dur._rep < 0) != (fac.is_signed && fac.bits.as_signed < 0)) { + // Different signs: Neg × Pos = Neg + ret = mlib_duration_min (); + } else { + // Same signs: Pos × Pos = Pos + // Neg × Neg = Pos + ret = mlib_duration_max (); + } + } else { + ret._rep = (intmax_t) bits; + } + return ret; +} + +// Duration scalar divide +#define _mlibDurationInfixOperator_div(LHS, Div) \ + _mlibDurationDivide (_mlibDurationArgument (LHS), mlib_upsize_integer (Div)) +static inline mlib_duration +_mlibDurationDivide (mlib_duration a, mlib_upsized_integer div) mlib_noexcept +{ + mlib_check (div.bits.as_unsigned, neq, 0); + if ((div.is_signed && div.bits.as_signed == -1) // + && a._rep == mlib_minof (mlib_duration_rep_t)) { + // MIN / -1 is UB, but the saturating result is the max + a = mlib_duration_max (); + } else { + if (div.is_signed) { + a._rep /= div.bits.as_signed; + } else { + a._rep /= div.bits.as_unsigned; + } + } + return a; +} + +// Duration addition +#define _mlibDurationInfixOperator_plus(LHS, RHS) \ + _mlibDurationAdd (_mlibDurationArgument (LHS), _mlibDurationArgument (RHS)) +static inline mlib_duration +_mlibDurationAdd (const mlib_duration a, const mlib_duration b) mlib_noexcept +{ + mlib_duration ret = {0}; + if (mlib_add (&ret._rep, a._rep, b._rep)) { + if (a._rep > 0) { + ret = mlib_duration_max (); + } else { + ret = mlib_duration_min (); + } + } + return ret; +} + +// Duration subtraction +#define _mlibDurationInfixOperator_minus(LHS, RHS) \ + _mlibDurationSubtract (_mlibDurationArgument (LHS), _mlibDurationArgument (RHS)) +static inline mlib_duration +_mlibDurationSubtract (const mlib_duration a, const mlib_duration b) mlib_noexcept +{ + mlib_duration ret = {0}; + if (mlib_sub (&ret._rep, a._rep, b._rep)) { + if (a._rep < 0) { + ret = mlib_duration_min (); + } else { + ret = mlib_duration_max (); + } + } + return ret; +} + +#define _mlibDurationInfixOperator_min(Duration, RHS) \ + _mlibDurationMinBetween (_mlibDurationArgument (Duration), _mlibDurationArgument (RHS)) +static inline mlib_duration +_mlibDurationMinBetween (mlib_duration lhs, mlib_duration rhs) +{ + if (lhs._rep < rhs._rep) { + return lhs; + } + return rhs; +} + +#define _mlibDurationInfixOperator_max(Duration, RHS) \ + _mlibDurationMaxBetween (_mlibDurationArgument (Duration), _mlibDurationArgument (RHS)) +static inline mlib_duration +_mlibDurationMaxBetween (mlib_duration lhs, mlib_duration rhs) +{ + if (lhs._rep > rhs._rep) { + return lhs; + } + return rhs; +} + +/** + * @brief Create a duration object from a count of some unit of time + * + * @param Count An integral expression + * @param Unit A unit suffix identifier, must be one of: + * + * - `ns` (nanoseconds) + * - `us` (microseconds) + * - `ms` (milliseconds) + * - `s` (seconds) + * - `min` (minutes) + * - `h` (hours) + * + * Other unit suffixes will generate a compile-time error + */ +#define mlib_duration_with_unit(Count, Unit) \ + MLIB_PASTE (_mlibCreateDurationFromUnitCount_, Unit) (mlib_upsize_integer (Count)) + +static inline mlib_duration +_mlibCreateDurationFromUnitCount_us (const mlib_upsized_integer n) mlib_noexcept +{ + mlib_duration ret = mlib_duration (); + if (n.is_signed) { + // The duration rep is the same as the signed max type, so we don't need to do any + // special arithmetic to encode it + mlib_static_assert (sizeof (mlib_duration_rep_t) == sizeof (n.bits.as_signed)); + ret._rep = mlib_assert_narrow (mlib_duration_rep_t, n.bits.as_signed); + } else { + if (mlib_narrow (&ret._rep, n.bits.as_unsigned)) { + // Unsigned value is too large to fit in our signed repr, so just use the max repr + ret = mlib_duration_max (); + } + } + return ret; +} + +static inline mlib_duration +_mlibCreateDurationFromUnitCount_ns (mlib_upsized_integer n) mlib_noexcept +{ + // We encode as a count of microseconds, so we lose precision here. + if (n.is_signed) { + n.bits.as_signed /= 1000; + } else { + n.bits.as_unsigned /= 1000; + } + return _mlibCreateDurationFromUnitCount_us (n); +} + +static inline mlib_duration +_mlibCreateDurationFromUnitCount_ms (const mlib_upsized_integer n) mlib_noexcept +{ + return mlib_duration (_mlibCreateDurationFromUnitCount_us (n), mul, 1000); +} + +static inline mlib_duration +_mlibCreateDurationFromUnitCount_s (const mlib_upsized_integer n) +{ + return mlib_duration (_mlibCreateDurationFromUnitCount_us (n), mul, 1000 * 1000); +} + +static inline mlib_duration +_mlibCreateDurationFromUnitCount_min (const mlib_upsized_integer n) +{ + return mlib_duration (_mlibCreateDurationFromUnitCount_us (n), mul, 60 * 1000 * 1000); +} + +static inline mlib_duration +_mlibCreateDurationFromUnitCount_h (const mlib_upsized_integer n) +{ + return mlib_duration (_mlibCreateDurationFromUnitCount_min (n), mul, 60); +} + +/** + * @brief Compare two durations + * + * @retval <0 If `a` is less-than `b` + * @retval >0 If `b` is less-than `a` + * @retval 0 If `a` and `b` are equal durations + * + * @note This is a function-like macro that can be called with an infix operator + * as the second argument to do natural duration comparisons: + * + * ``` + * mlib_duration_cmp(, , ) + * ``` + * + * Where each `` should be an arglist for @see mlib_duration + */ +static inline enum mlib_cmp_result +mlib_duration_cmp (const mlib_duration a, const mlib_duration b) mlib_noexcept +{ + return mlib_cmp (a._rep, b._rep); +} + +#define mlib_duration_cmp(...) MLIB_ARGC_PICK (_mlibDurationCmp, __VA_ARGS__) +#define _mlibDurationCmp_argc_2 mlib_duration_cmp +#define _mlibDurationCmp_argc_3(Left, Op, Right) \ + (mlib_duration_cmp (mlib_duration_arg (Left), mlib_duration_arg (Right)) Op 0) + +/** + * @brief Obtain an mlib_duration that corresponds to a `timespec` value + * + * @note The `timespec` type may represent times outside of the range of, or + * more precise than, what is representable in `mlib_duration`. In such case, + * the returned duration will be the nearest representable duration, rounded + * toward zero. + */ +static inline mlib_duration +mlib_duration_from_timespec (const struct timespec ts) mlib_noexcept +{ + return mlib_duration ((ts.tv_sec, s), plus, (ts.tv_nsec, ns)); +} + +/** + * @brief Create a C `struct timespec` that corresponds to the given duration + * + * @param d The duration to be converted + * @return struct timespec A timespec that represents the same durations + */ +static inline struct timespec +mlib_duration_to_timespec (const mlib_duration d) mlib_noexcept +{ + // Number of full seconds in the duration + const mlib_duration_rep_t n_full_seconds = mlib_seconds_count (d); + // Duration with full seconds removed + const mlib_duration usec_part = mlib_duration (d, minus, (n_full_seconds, s)); + // Number of microseconds in the duration, minus all full seconds + const mlib_duration_rep_t n_remaining_microseconds = mlib_microseconds_count (usec_part); + // Compute the number of nanoseconds: + const int32_t n_nsec = mlib_assert_mul (int32_t, n_remaining_microseconds, 1000); + struct timespec ret; + ret.tv_sec = n_full_seconds; + ret.tv_nsec = n_nsec; + return ret; +} + +mlib_extern_c_end (); + +#endif // MLIB_DURATION_H_INCLUDED diff --git a/src/common/src/mlib/platform.h b/src/common/src/mlib/platform.h new file mode 100644 index 0000000000..36f502608e --- /dev/null +++ b/src/common/src/mlib/platform.h @@ -0,0 +1,54 @@ +/** + * @file mlib/platform.h + * @brief Operating System Headers and Definitions + * @date 2025-04-21 + * + * This file will conditionally include the general system headers available + * for the current host platform. + * + * @copyright Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MLIB_PLATFORM_H_INCLUDED +#define MLIB_PLATFORM_H_INCLUDED + +// clang-format off + +// Windows headers +#ifdef _WIN32 + // Check that our WINNT version isn't too old to be used + #if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x601) + #undef _WIN32_WINNT + #endif + #ifndef _WIN32_WINNT + // Request a new-enough version of the Win32 API (required for MinGW) + #define _WIN32_WINNT 0x601 + #endif + #define NOMINMAX + // Winsock must be included before windows.h + #include + #include +#endif + +// POSIX headers +#if defined(__unix__) || defined(__unix) || defined(__APPLE__) + #include + #include + #include +#endif + +// clang-format on + +#endif // MLIB_PLATFORM_H_INCLUDED diff --git a/src/common/src/mlib/test.h b/src/common/src/mlib/test.h index 86f576b1af..9ce8b6c056 100644 --- a/src/common/src/mlib/test.h +++ b/src/common/src/mlib/test.h @@ -144,10 +144,30 @@ typedef struct mlib_source_location { // Integer not-equal: #define _mlibCheckCondition_neq(A, B) \ _mlibCheckIntCmp ( \ - mlib_equal, false, "!=", mlib_upsize_integer (A), mlib_upsize_integer (B), #A, #B, mlib_this_source_location ()) + mlib_equal, false, "≠", mlib_upsize_integer (A), mlib_upsize_integer (B), #A, #B, mlib_this_source_location ()) // Simple assertion with an explanatory string #define _mlibCheckCondition_because(Cond, Msg) \ _mlibCheckConditionBecause (Cond, #Cond, Msg, mlib_this_source_location ()) +// Integer comparisons: +#define _mlibCheckCondition_lt(A, B) \ + _mlibCheckIntCmp ( \ + mlib_less, true, "<", mlib_upsize_integer (A), mlib_upsize_integer (B), #A, #B, mlib_this_source_location ()) +#define _mlibCheckCondition_lte(A, B) \ + _mlibCheckIntCmp (mlib_greater, \ + false, \ + "≤", \ + mlib_upsize_integer (A), \ + mlib_upsize_integer (B), \ + #A, \ + #B, \ + mlib_this_source_location ()) +#define _mlibCheckCondition_gt(A, B) \ + _mlibCheckIntCmp ( \ + mlib_greater, true, ">", mlib_upsize_integer (A), mlib_upsize_integer (B), #A, #B, mlib_this_source_location ()) +#define _mlibCheckCondition_gte(A, B) \ + _mlibCheckIntCmp ( \ + mlib_less, false, "≥", mlib_upsize_integer (A), mlib_upsize_integer (B), #A, #B, mlib_this_source_location ()) + /// Check evaluator when given a single boolean static inline void diff --git a/src/common/src/mlib/time_point.h b/src/common/src/mlib/time_point.h new file mode 100644 index 0000000000..f2e6b472f5 --- /dev/null +++ b/src/common/src/mlib/time_point.h @@ -0,0 +1,348 @@ +/** + * @file mlib/time_point.h + * @brief A point-in-time type + * @date 2025-04-17 + * + * The `mlib_time_point` type represents a stable point-in-time. The time point + * itself is relative to a monotonic clock for the program, so it should not be + * transmitted or persisted outside of the execution of a program that uses it. + * + * @copyright Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MLIB_TIME_POINT_H_INCLUDED +#define MLIB_TIME_POINT_H_INCLUDED + +#include +#include +#include +#include + +// Check for POSIX clock functions functions +#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || (defined(_DEFAULT_SOURCE) && !defined(_WIN32)) +#include +#undef mlib_have_posix_clocks +#define mlib_have_posix_clocks() 1 +#endif + +#ifndef mlib_have_posix_clocks +#define mlib_have_posix_clocks() 0 +#endif + +#include +#include +#include + +mlib_extern_c_begin (); + +/** + * @brief An abstract point-in-time type + * + * The time point is encoded as a duration relative to some stable reference point + * provided by the system. See the docs for the `time_since_monotonic_start` + * member for more details. + * + * At time of writing, there is no easy way to convert this monotonic time point + * into a human-readable wall time. Thus, the time-point itself is abstract. + */ +typedef struct mlib_time_point { + /** + * @brief The encoding of the time point as a duration relative to some + * unspecified stable real point in time. + * + * It is important to understand the nature of the reference point: `mlib_now()` + * uses the system's monotonic high-resolution clock, which has an unspecified + * reference point in the past. That stable reference point may change between + * program executions, so it is not safe to store/transmit this value outside + * of the current program execution. + * + * If you attempt to store a duration in this member that is with respect to + * some other clock, then the resulting time point object will have an unspecified + * relationship to other time points created with different clocks. For this reason, + * this member should not be set to any absolute values, and should only be adjusted + * relative to its current value. + */ + mlib_duration time_since_monotonic_start; +} mlib_time_point; + +/** + * @brief Given two time points, selects the time point that occurs earliest + */ +static inline mlib_time_point +mlib_earliest (mlib_time_point l, mlib_time_point r) +{ + l.time_since_monotonic_start = mlib_duration (l.time_since_monotonic_start, min, r.time_since_monotonic_start); + return l; +} + +/** + * @brief Given two time points, selects the time point that occurs later + */ +static inline mlib_time_point +mlib_latest (mlib_time_point l, mlib_time_point r) +{ + l.time_since_monotonic_start = mlib_duration (l.time_since_monotonic_start, max, r.time_since_monotonic_start); + return l; +} + +/** + * @brief Obtain the integer clock ID that is used by `mlib_now()` to obtain + * the time. This value only has meaning on POSIX systems. On Win32, returns + * INT_MIN. + * + * @return int The integer clock ID, corresponding to the value of a `CLOCK_...` + * object macro. + */ +static inline int +mlib_now_clockid (void) mlib_noexcept +{ +#ifdef CLOCK_MONOTONIC_RAW + // Linux had a bad definition of CLOCK_MONOTONIC, which would jump based on NTP adjustments. + // They replaced it with CLOCK_MONOTONIC_RAW, which is stable and cannot be adjusted. + return CLOCK_MONOTONIC_RAW; +#elif defined(CLOCK_MONOTONIC) + return CLOCK_MONOTONIC; +#else + return INT_MIN; +#endif +} + +/** + * @brief Obtain a point-in-time corresponding to the current time + */ +static inline mlib_time_point +mlib_now (void) mlib_noexcept +{ +#if mlib_have_posix_clocks() + // Use POSIX clock_gettime + struct timespec ts; + int rc = clock_gettime (mlib_now_clockid (), &ts); + // The above call must never fail: + mlib_check (rc, eq, 0); + // Encode the time point: + mlib_time_point ret; + ret.time_since_monotonic_start = mlib_duration_from_timespec (ts); + return ret; +#elif mlib_is_win32() + // Win32 APIs for the high-performance monotonic counter. These APIs never fail after Windows XP + LARGE_INTEGER freq; + QueryPerformanceFrequency (&freq); + LARGE_INTEGER lits; + QueryPerformanceCounter (&lits); + // Number of ticks of the perf counter + const int64_t ticks = lits.QuadPart; + // Number of ticks that the counter emits in one second + const int64_t ticks_per_second = freq.QuadPart; + // Do some math that avoids an integer overflow when converting to microseconds. + // Just one million, used to convert time units to microseconds. + const int64_t one_million = 1000000; + // Number of whole seconds that have elapsed: + const int64_t whole_seconds = ticks / ticks_per_second; + // Number of microseconds beyond the last whole second: + const int64_t subsecond_us = ((ticks % ticks_per_second) * one_million) / ticks_per_second; + mlib_time_point ret; + ret.time_since_monotonic_start = mlib_duration ((whole_seconds, s), plus, (subsecond_us, us)); + return ret; +#else +#error We do not know how to get the current time on this platform +#endif +} + +/** + * @brief Obtain a point-in-time relative to a base time offset by the given + * duration (which may be negative). + * + * @param from The basis of the time offset + * @param delta The amount of time to shift the resulting time point + * @return mlib_time_point If 'delta' is a positive duration, the result is a + * point-in-time *after* 'from'. If 'delta' is a negative duration, the result + * is a point-in-time *before* 'from'. + */ +static inline mlib_time_point +mlib_time_add (mlib_time_point from, mlib_duration delta) mlib_noexcept +{ + mlib_time_point ret; + ret.time_since_monotonic_start = mlib_duration (from.time_since_monotonic_start, plus, delta); + return ret; +} + +#define mlib_time_add(From, Delta) mlib_time_add (From, mlib_duration_arg (Delta)) + +/** + * @brief Obtain the duration between two points in time. + * + * @param stop The target time + * @param start The base time + * @return mlib_duration The amount of time you would need to wait starting + * at 'start' for the time to become 'stop' (the result may be a negative + * duration). + * + * Intuition: If "stop" is "in the future" relative to "start", you will + * receive a positive duration, indicating an amount of time to wait + * beginning at 'start' to reach 'stop'. If "stop" is actually *before* + * "start", you will receive a paradoxical *negative* duration, indicating + * the amount of time needed to time-travel backwards to reach "stop." + */ +static inline mlib_duration +mlib_time_difference (mlib_time_point stop, mlib_time_point start) +{ + return mlib_duration (stop.time_since_monotonic_start, minus, start.time_since_monotonic_start); +} + +/** + * @brief Obtain the amount of time that has elapsed since the time point `t`, + * or a negative duration if the time is in the future. + * + * @param t The time point to be inspected + * @return mlib_duration If `t` is in the past, returns the duration of time + * that has elapsed since that point-in-time. If `t` is in the future, returns + * a negative time representing the amount of time that be be waited until we + * reach `t`. + */ +static inline mlib_duration +mlib_elapsed_since (mlib_time_point t) +{ + return mlib_time_difference (mlib_now (), t); +} + +/** + * @brief Compare two time points to create an ordering. + * + * A time point "in the past" is "less than" a time point "in the future". + * + * @retval <0 If 'a' is before 'b' + * @retval >0 If 'b' is before 'a' + * @retval 0 If 'a' and 'b' are equivalent + * + * @note This is a function-like macro that can be called with an infix operator + * as the second argument to do natural time-point comparisons: + * + * ``` + * mlib_time_cmp(a, <=, b) + * ``` + */ +static inline enum mlib_cmp_result +mlib_time_cmp (mlib_time_point a, mlib_time_point b) mlib_noexcept +{ + return mlib_duration_cmp (a.time_since_monotonic_start, b.time_since_monotonic_start); +} + +#define mlib_time_cmp(...) MLIB_ARGC_PICK (_mlib_time_cmp, __VA_ARGS__) +#define _mlib_time_cmp_argc_2 mlib_time_cmp +#define _mlib_time_cmp_argc_3(L, Op, R) (mlib_time_cmp ((L), (R)) Op 0) + +/** + * @brief Pause the calling thread until at least the specified duration has elapsed. + * + * @param d The duration of time to pause the thread. If this duration is zero + * or negative, then this function returns immediately. + * @return int An error code, if any occurred. Returns zero upon success, or + * the system's error number value (`errno` on POSIX, `GetLastError()` on + * Windows) + */ +static inline int +mlib_sleep_for (const mlib_duration d) mlib_noexcept +{ + mlib_duration_rep_t duration_usec = mlib_microseconds_count (d); + if (duration_usec <= 0) { + // Don't sleep any time + return 0; + } +#if mlib_have_posix_clocks() + // Convert the microseconds count to the value for the usleep function. We don't + // know the precise integer type that `usleep` expects, so do a checked-narrow + // to handle too-large values. + useconds_t i = 0; + if (mlib_narrow (&i, duration_usec)) { + // Too many microseconds. Sleep for the max. This will only be reached + // for positive durations because of the above check against `<= 0` + i = mlib_maxof (useconds_t); + } + int rc = usleep (i); + if (rc != 0) { + return errno; + } + return 0; +#elif defined(_WIN32) + DWORD retc = 0; + // Use WaitableTimer + const HANDLE timer = CreateWaitableTimerW (/* no attributes */ NULL, + /* Manual reset */ true, + /* Unnamed */ NULL); + // Check that we actually succeeded in creating a timer. + if (!timer) { + retc = GetLastError (); + goto done; + } + // Convert the number of microseconds into a count of 100ns intervals. Use + // a negative value to request a relative sleep time. + LONGLONG negative_n_100ns_units = 0; + if (mlib_mul (&negative_n_100ns_units, duration_usec, -10)) { + // Too many units. Clamp to the max duration (negative for a relative + // sleep): + negative_n_100ns_units = mlib_minof (LONGLONG); + } + LARGE_INTEGER due_time; + due_time.QuadPart = negative_n_100ns_units; + BOOL okay = SetWaitableTimer (/* The timer to modify */ timer, + /* The time after which it will fire */ &due_time, + /* Interval period 0 = only fire once */ 0, + /* No completion routine */ NULL, + /* No arg for no completion routine */ NULL, + /* Wake up the system if it goes to sleep */ true); + if (!okay) { + // Failed to set the timer. Hmm? + retc = GetLastError (); + goto done; + } + // Do the actual wait + DWORD rc = WaitForSingleObject (timer, INFINITE); + if (rc == WAIT_FAILED) { + // Executing the wait operation failed. + retc = GetLastError (); + goto done; + } + // Check for success: + mlib_check (rc, eq, WAIT_OBJECT_0); +done: + // Done with the timer. + if (timer) { + CloseHandle (timer); + } + return retc; +#else +#error "mlib_sleep_for" is not implemented on this platform. +#endif +} + +#define mlib_sleep_for(...) mlib_sleep_for (mlib_duration (__VA_ARGS__)) + +/** + * @brief Pause the calling thread until the given time point has been reached + * + * @param when The time point at which to resume, at soonest + * @return int A possible error code for the operation. Returns zero upon success. + * + * The `when` is the *soonest* successful wake time. The thread may wake at a later time. + */ +static inline int +mlib_sleep_until (const mlib_time_point when) mlib_noexcept +{ + const mlib_duration time_until = mlib_time_difference (when, mlib_now ()); + return mlib_sleep_for (time_until); +} + +mlib_extern_c_end (); + +#endif // MLIB_TIME_POINT_H_INCLUDED diff --git a/src/common/src/mlib/timer.h b/src/common/src/mlib/timer.h new file mode 100644 index 0000000000..03cc754978 --- /dev/null +++ b/src/common/src/mlib/timer.h @@ -0,0 +1,173 @@ +/** + * @file mlib/timer.h + * @brief Timer types and functions + * @date 2025-04-18 + * + * This file contains APIs for creating fixed-deadline timer objects that represent + * stable expiration points. + * + * @copyright Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MLIB_TIMER_H_INCLUDED +#define MLIB_TIMER_H_INCLUDED + +#include +#include +#include + +mlib_extern_c_begin (); + +/** + * @brief Represents an expiry timer. The timer stores some point-in-time + * after which it is considered to have "expired." + */ +typedef struct mlib_timer { + /** + * @brief The point-in-time at which the timer will be considered expired. + * + * This field can be updated or modified to change the expiration time of + * the timer. + */ + mlib_time_point expires_at; +} mlib_timer; + +/** + * @brief Create a deadline timer that expires at the given point-in-time + * + * @param t The point-in-time at which the returned timer should be expired + * @return mlib_timer + */ +static inline mlib_timer +mlib_expires_at (const mlib_time_point t) mlib_noexcept +{ + mlib_timer ret; + ret.expires_at = t; + return ret; +} + +/** + * @brief Create a deadline timer that expires after the given duration has + * elapsed from the point-in-time at which this function is called + */ +static inline mlib_timer +mlib_expires_after (const mlib_duration dur) mlib_noexcept +{ + const mlib_time_point later = mlib_time_add (mlib_now (), dur); + return mlib_expires_at (later); +} + +#define mlib_expires_after(...) mlib_expires_after (mlib_duration (__VA_ARGS__)) + +/** + * @brief Obtain a timer that will "never" expire + * + * In actuality, the timer expires at a time so far in the future that no computer + * program could ever hope to continue running to that point, and by the time + * that point is reached it will be some other civilization's problem. + */ +static inline mlib_timer +mlib_expires_never (void) mlib_noexcept +{ + mlib_timer t; + t.expires_at.time_since_monotonic_start = mlib_duration_max (); + return t; +} + +/** + * @brief Between two timers, return the timer that will expire the soonest + */ +static inline mlib_timer +mlib_soonest_timer (mlib_timer l, mlib_timer r) mlib_noexcept +{ + l.expires_at = mlib_earliest (l.expires_at, r.expires_at); + return l; +} + +/** + * @brief Obtain the duration of time that is remaining until the given timer + * expires. If the timer has expired, the returned duration will be zero (never + * negative) + */ +static inline mlib_duration +mlib_timer_remaining (const mlib_timer timer) mlib_noexcept +{ + // The duration until the expiry time of the timer + const mlib_duration remain = mlib_time_difference (timer.expires_at, mlib_now ()); + if (mlib_duration_cmp (remain, <, mlib_duration ())) { + // No time remaining. Return a zero duration (not a negative duration) + return mlib_duration (); + } + return remain; +} + +/** + * @brief Test for timer expiration + * + * @param timer The timer to be tested + * @param once (Optional) A pointer to an optional once-flag that will be set + * to `true` (see below) + * + * The function behaves as follows: + * + * - If `once` is a null pointer, then returns a boolean indicating whether the + * timer has expired. + * - Otherwise, if `*once` is `true`: + * - If `timer` has expired, returns `true` + * - Otherwise, `*once` is set to `true` and returns `false` + * + * The intent of the `once` flag is to support loops that check for expiry, + * where at least one iteration of the loop *must* be attempted, even if the + * timer has expired. For example: + * + * ``` + * void do_thing() { + * bool once = false; + * while (!mlib_timer_is_expired(timer, &once)) { + * try_thing(timer); + * } + * } + * ``` + * + * In the above, `try_thing` will be called *at least once*, even if the timer + * is already expired. + */ +static inline bool +mlib_timer_is_expired (const mlib_timer timer, bool *once) mlib_noexcept +{ + // Is the timer already expired? + const bool no_time_remaining = mlib_time_cmp (timer.expires_at, <=, mlib_now ()); + if (!once) { + // Just return `true` if there is zero time remaining + return no_time_remaining; + } else { + // Tweak behavior based on the `*once` value + if (!*once) { + // This is the first time we have been called with the given once-flag + *once = true; + // Don't count an expiration, even if we have zero time left, because + // the caller wants to try some operation at least once + return false; + } + return no_time_remaining; + } +} + +mlib_extern_c_end (); + +#define mlib_timer_is_expired(...) MLIB_ARGC_PICK (_mlibTimerIsExpired, __VA_ARGS__) +#define _mlibTimerIsExpired_argc_1(Timer) mlib_timer_is_expired ((Timer), NULL) +#define _mlibTimerIsExpired_argc_2(Timer, OncePtr) mlib_timer_is_expired ((Timer), (OncePtr)) + +#endif // MLIB_TIMER_H_INCLUDED diff --git a/src/common/tests/test-mlib.c b/src/common/tests/test-mlib.c index 0d23a34791..86bb9f57c3 100644 --- a/src/common/tests/test-mlib.c +++ b/src/common/tests/test-mlib.c @@ -1,11 +1,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include @@ -50,6 +53,26 @@ _test_checks (void) mlib_assert_aborts () { mlib_check (false, because, "this will fail"); } + // lt + mlib_check (1, lt, 4); + mlib_assert_aborts () { + mlib_check (4, lt, 1); + } + mlib_check (1, lte, 4); + mlib_check (1, lte, 1); + mlib_assert_aborts () { + mlib_check (4, lte, 3); + } + // gt + mlib_check (4, gt, 2); + mlib_assert_aborts () { + mlib_check (3, gt, 5); + } + mlib_check (3, gte, 2); + mlib_check (3, gte, 3); + mlib_assert_aborts () { + mlib_check (3, gte, 5); + } } static void @@ -887,6 +910,197 @@ _test_str_view (void) } } +static void +_test_duration (void) +{ + mlib_duration d = mlib_duration (); + mlib_check (mlib_microseconds_count (d), eq, 0); + + // Creating durations with the macro name + d = mlib_duration (); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration ())); + d = mlib_duration (d); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration ())); + + d = mlib_duration (10, ms); + mlib_check (mlib_duration_cmp (d, ==, (10, ms))); + d = mlib_duration (10, us); + mlib_check (mlib_duration_cmp (d, ==, (10, us))); + d = mlib_duration (10, s); + mlib_check (mlib_duration_cmp (d, ==, (10, s))); + + d = mlib_duration ((10, s), mul, 3); + mlib_check (mlib_duration_cmp (d, ==, (30, s))); + + d = mlib_duration ((10, s), plus, (40, ms)); + mlib_check (mlib_duration_cmp (d, ==, (10040, ms))); + + d = mlib_duration ((10, s), div, 20); + mlib_check (mlib_duration_cmp (d, ==, (500, ms))); + + d = mlib_duration ((4, s), min, (400, ms)); + mlib_check (mlib_duration_cmp (d, ==, (400, ms))); + + d = mlib_duration (10, min); + mlib_check (mlib_duration_cmp (d, ==, (600, s))); + + d = mlib_duration (4, h); + mlib_check (mlib_duration_cmp (d, ==, (240, min))); + + d = mlib_duration ((10, s), div, 20); + d = mlib_duration ( + // At least 5 seconds: + (d, max, (5, s)), + // At most 90 seconds: + min, + (90, s)); + mlib_check (mlib_duration_cmp (d, ==, (5, s))); + + // Comparison + mlib_check (mlib_duration_cmp (mlib_duration (4, s), mlib_duration (4, s)) == 0); + mlib_check (mlib_duration_cmp (mlib_duration (4, s), mlib_duration (5, s)) < 0); + mlib_check (mlib_duration_cmp (mlib_duration (4, s), mlib_duration (-5, s)) > 0); + mlib_check (mlib_duration_cmp ((4, s), ==, (4, s))); + mlib_check (mlib_duration_cmp ((4, s), <, (5, s))); + mlib_check (mlib_duration_cmp ((4, s), >, (-5, s))); + + // Overflow saturates: + d = mlib_duration (mlib_maxof (mlib_duration_rep_t), s); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + + d = mlib_duration (d, mul, 16); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + + // Rounds toward zero + d = mlib_duration (1050, ms); + mlib_check (mlib_seconds_count (d), eq, 1); + d = mlib_duration (-1050, ms); + mlib_check (mlib_seconds_count (d), eq, -1); + d = mlib_duration (1729, us); + mlib_check (mlib_milliseconds_count (d), eq, 1); + d = mlib_duration (-1729, us); + mlib_check (mlib_milliseconds_count (d), eq, -1); + + d = mlib_duration ((-3, s), plus, mlib_duration_min ()); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_min ())); + d = mlib_duration ((4, s), plus, mlib_duration_max ()); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + + d = mlib_duration ((4, s), minus, (2271, ms)); + mlib_check (mlib_milliseconds_count (d), eq, 1729); + // Overflow saturates: + d = mlib_duration ((-4, ms), minus, mlib_duration_max ()); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_min ())); + d = mlib_duration ((4, ms), minus, mlib_duration_min ()); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + + d = mlib_duration ((4, s), mul, 5); + mlib_check (mlib_duration_cmp (d, ==, (20, s))); + d = mlib_duration (mlib_duration_max (), mul, 2); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + d = mlib_duration (mlib_duration_max (), mul, -2); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_min ())); + d = mlib_duration (mlib_duration_min (), mul, 2); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_min ())); + d = mlib_duration (mlib_duration_min (), mul, -2); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + + d = mlib_duration (mlib_duration_max (), div, -1); + mlib_check (mlib_duration_cmp (d, mlib_duration ()) < 0); + d = mlib_duration (mlib_duration_min (), div, -1); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration_max ())); + mlib_assert_aborts () { + // Division by zero + d = mlib_duration (d, div, 0); + } + + // To/from timespec + struct timespec ts; + ts.tv_sec = 4; + ts.tv_nsec = 0; + d = mlib_duration_from_timespec (ts); + mlib_check (mlib_duration_cmp (d, ==, (4, s))); + // + ts.tv_sec = -3; + ts.tv_nsec = -4000; + d = mlib_duration_from_timespec (ts); + mlib_check (mlib_duration_cmp (d, ==, mlib_duration ((-3000004, us), plus, (0, us)))); + // + ts = mlib_duration_to_timespec (mlib_duration (-5000908, us)); + mlib_check (ts.tv_sec, eq, -5); + mlib_check (ts.tv_nsec, eq, -908000); +} + +static void +_test_time_point (void) +{ + mlib_time_point t = mlib_now (); + + // Offset the time point + mlib_time_point later = mlib_time_add (t, (1, s)); + mlib_check (mlib_time_cmp (t, <, later)); + + // Difference between two time points is a duration: + mlib_duration diff = mlib_time_difference (later, t); + mlib_check (mlib_milliseconds_count (diff), eq, 1000); + + // The time is only ever monotonically increasing + mlib_foreach_urange (i, 10000) { + (void) i; + mlib_check (mlib_time_cmp (t, <=, mlib_now ())); + t = mlib_now (); + } +} + +static void +_test_sleep (void) +{ + mlib_time_point start = mlib_now (); + int rc = mlib_sleep_for (mlib_duration (50, ms)); + mlib_check (rc, eq, 0); + mlib_duration t = mlib_time_difference (mlib_now (), start); + mlib_check (mlib_milliseconds_count (t), gte, 45); + mlib_check (mlib_milliseconds_count (t), lt, 200); + + // Sleeping for a negative duration returns immediately with success + start = mlib_now (); + mlib_check (mlib_sleep_for (-10, s), eq, 0); + mlib_check (mlib_duration_cmp (mlib_elapsed_since (start), <, (100, ms))); + + // Sleeping until a point in the past returns immediately as well + mlib_check (mlib_sleep_until (start), eq, 0); + mlib_check (mlib_duration_cmp (mlib_elapsed_since (start), <, (100, ms))); +} + +static void +_test_timer (void) +{ + mlib_timer tm = mlib_expires_after (200, ms); + mlib_check (!mlib_timer_is_expired (tm)); + mlib_sleep_for (250, ms); + mlib_check (mlib_timer_is_expired (tm)); + + // Test the once-var condition + bool cond = false; + // Says not expired on the first call + mlib_check (!mlib_timer_is_expired (tm, &cond)); + mlib_check (cond); // Was set to `true` + // Says it was expired on this call: + mlib_check (mlib_timer_is_expired (tm, &cond)); + + // Try with a not-yet-expired timer + cond = false; + tm = mlib_expires_after (10, s); + mlib_check (!mlib_timer_is_expired (tm)); + mlib_check (!mlib_timer_is_expired (tm, &cond)); + // cond was set to `true`, even though we are not yet expired + mlib_check (cond); + + // Create a timer that expired in the past + tm = mlib_expires_at (mlib_time_add (mlib_now (), (-10, s))); + mlib_check (mlib_timer_is_expired (tm)); +} + void test_mlib_install (TestSuite *suite) { @@ -903,6 +1117,10 @@ test_mlib_install (TestSuite *suite) TestSuite_Add (suite, "/mlib/check-cast", _test_cast); TestSuite_Add (suite, "/mlib/ckdint-partial", _test_ckdint_partial); TestSuite_Add (suite, "/mlib/str_view", _test_str_view); + TestSuite_Add (suite, "/mlib/duration", _test_duration); + TestSuite_Add (suite, "/mlib/time_point", _test_time_point); + TestSuite_Add (suite, "/mlib/sleep", _test_sleep); + TestSuite_Add (suite, "/mlib/timer", _test_timer); } mlib_diagnostic_pop (); diff --git a/src/libbson/src/bson/bson-clock.c b/src/libbson/src/bson/bson-clock.c index cb8234e9ce..4f98894e64 100644 --- a/src/libbson/src/bson/bson-clock.c +++ b/src/libbson/src/bson/bson-clock.c @@ -17,6 +17,9 @@ #include #include +#include +#include + #if defined(BSON_HAVE_CLOCK_GETTIME) #include @@ -110,24 +113,6 @@ bson_gettimeofday (struct timeval *tv) /* OUT */ int64_t bson_get_monotonic_time (void) { -#if defined(BSON_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) - struct timespec ts; - /* ts.tv_sec may be a four-byte integer on 32 bit machines, so cast to - * int64_t to avoid truncation. */ - clock_gettime (CLOCK_MONOTONIC, &ts); - return (((int64_t) ts.tv_sec * 1000000) + (ts.tv_nsec / 1000)); -#elif defined(_WIN32) - /* Despite it's name, this is in milliseconds! */ - int64_t ticks = GetTickCount64 (); - return (ticks * 1000); -#elif defined(__hpux__) - int64_t nanosec = gethrtime (); - return (nanosec / 1000UL); -#else -#pragma message "Monotonic clock is not yet supported on your platform." - struct timeval tv; - - bson_gettimeofday (&tv); - return ((int64_t) tv.tv_sec * 1000000) + tv.tv_usec; -#endif + mlib_time_point now = mlib_now (); + return mlib_microseconds_count (now.time_since_monotonic_start); } diff --git a/src/libmongoc/src/mongoc/mongoc-async-cmd-private.h b/src/libmongoc/src/mongoc/mongoc-async-cmd-private.h index 49ecd2bc0e..27793fa367 100644 --- a/src/libmongoc/src/mongoc/mongoc-async-cmd-private.h +++ b/src/libmongoc/src/mongoc/mongoc-async-cmd-private.h @@ -30,75 +30,327 @@ #include +#include +#include +#include + BSON_BEGIN_DECLS typedef enum { - MONGOC_ASYNC_CMD_INITIATE, - MONGOC_ASYNC_CMD_SETUP, + // The command has no stream and needs to connect to a peer + MONGOC_ASYNC_CMD_PENDING_CONNECT, + // The command has connected and has a stream, but needs to run stream setup (e.g. TLS handshake) + MONGOC_ASYNC_CMD_STREAM_SETUP, + // The command has data to send to the peer MONGOC_ASYNC_CMD_SEND, + // The command is ready to receive the response length header MONGOC_ASYNC_CMD_RECV_LEN, + // The command is ready to receive the RPC message MONGOC_ASYNC_CMD_RECV_RPC, + // The command is in an invalid error state MONGOC_ASYNC_CMD_ERROR_STATE, - MONGOC_ASYNC_CMD_CANCELED_STATE, + // The command has been cancelled. + MONGOC_ASYNC_CMD_CANCELLED_STATE, } mongoc_async_cmd_state_t; +/** + * @brief Command callback/state result code + */ +typedef enum { + MONGOC_ASYNC_CMD_CONNECTED, + MONGOC_ASYNC_CMD_IN_PROGRESS, + MONGOC_ASYNC_CMD_SUCCESS, + MONGOC_ASYNC_CMD_ERROR, + MONGOC_ASYNC_CMD_TIMEOUT, +} mongoc_async_cmd_result_t; + +/** + * @brief Callback type associated with an asynchronous command object. + * + * The callback will be invoked after a new connection is established, and + * again when the command completes. + * + * @param acmd Pointer to the async command object that invoked the callback + * @param result The result/state of the asynchronous command object + * @param bson Result data associated with the command's state, if any. + * @param duration The elapsed duration that the command object has been running. + * This will be zero when the CONNECTED state is invoked. + */ +typedef void (*mongoc_async_cmd_event_cb) (struct _mongoc_async_cmd *acmd, + mongoc_async_cmd_result_t result, + const bson_t *bson, + mlib_duration duration); + +/** + * @brief Callback that is used to open a new stream for a command object. + * + * If the function returns a null pointer, it is considered to have failed. + */ +typedef mongoc_stream_t *(*mongoc_async_cmd_connect_cb) (struct _mongoc_async_cmd *); + +/** + * @brief Stream setup callback for asynchronous commands + * + * This callback will be invoked by the async command runner after a stream has + * been opened, allowing the command creator time to do setup on the stream + * before the command tries to use it. + * + * @param stream Pointer to the valid stream object that was just created + * @param events Pointer to a poll() events bitmask. The function can modify this + * value to change what the stream will await on next + * @param ctx Pointer to arbitrary user data for the setup function. + * @param timeout A timer that gives a deadline for the setup operation + * @return int The function should return -1 on failure, 1 if the stream + * immediately has data to send, or 0 for generic success. + */ +typedef int (*mongoc_async_cmd_stream_setup_cb) ( + mongoc_stream_t *stream, int *events, void *ctx, mlib_timer timeout, bson_error_t *error); + + typedef struct _mongoc_async_cmd { + /** + * @brief The stream that is associated with an in-progress command. + * + * This may start as a null pointer, but is updated when a connection is + * established. + */ mongoc_stream_t *stream; + // Non-owning pointer to the asynchrony engine associated with this command mongoc_async_t *async; + /** + * @brief The current state of the asynchronous command. + * + * Used to control the state machine that is executed with async_cmd_run() + */ mongoc_async_cmd_state_t state; + // Bitmask of poll() events that this command is waiting to see int events; - mongoc_async_cmd_initiate_t initiator; - mongoc_async_cmd_setup_t setup; - void *setup_ctx; - mongoc_async_cmd_cb_t cb; - void *data; + /** + * @brief User-provided callback that will be used to lazily create the I/O stream + * for the command. + */ + mongoc_async_cmd_connect_cb _stream_connect; + /** + * @brief User-provided callback function to do setup on the command's stream + * after the stream has been created automatically. + */ + mongoc_async_cmd_stream_setup_cb _stream_setup; + // Arbitrary userdata pointer passed to the stream setup function + void *_stream_setup_userdata; + /** + * @brief User-provided command event callback. Invoked after a new + * connection is established, and again when the command completes. + */ + mongoc_async_cmd_event_cb _event_callback; + // Arbitrary userdata passed when the object was created + void *_userdata; + /** + * @brief Timer to when the command should attempt to lazily initiate a new + * connection with the _stream_connect callback. This does not apply if the + * command was given a stream upon construction. + */ + mlib_timer _connect_delay_timer; + /** + * @brief The "start" reference point-in-time for the command object + * + * This is used to determine how long the command has been in progress, + * including for when to consider the command to have timed-out. + * + * NOTE: This value can change! See: `_acmd_reset_elapsed` + */ + mlib_time_point _start_time; + /** + * @brief The timeout duration allotted to the command object. + * + * We need to store it as a duration rather than a timer, because we need to + * reset the timeout at certain points (see: `_acmd_reset_elapsed`) + */ + mlib_duration _timeout; + bson_error_t error; - int64_t initiate_delay_ms; - int64_t connect_started; - int64_t cmd_started; - int64_t timeout_msec; - bson_t cmd; + /** + * @brief The BSON document of the command to be executed on the server. + */ + bson_t _command; mongoc_buffer_t buffer; mongoc_iovec_t *iovec; size_t niovec; size_t bytes_written; size_t bytes_to_read; mcd_rpc_message *rpc; - bson_t reply; - bool reply_needs_cleanup; + /** + * @brief The response data from the peer. + * + * Initialized with BSON_INITIALIZER, so safe to pass/destroy upon construction. + */ + bson_t _response_data; char *ns; + /** + * @brief The DNS address info that was associated with the command when + * it was created. May be null if no DNS result was provided. + */ struct addrinfo *dns_result; struct _mongoc_async_cmd *next; struct _mongoc_async_cmd *prev; } mongoc_async_cmd_t; +/** + * @brief Create a new asynchronous command object associated with a collection + * of async commands + * + * @param async The async engine that will own this new command + * @param stream (Optional) a stream to be associated with the new command. If + * NULL, then a stream will be created lazily for the command object. + * @param dns_result Pointer to a DNS result associated with the command + * @param connect_callback Callback function that will be used to establish a + * new stream for the command object if `stream` is null. + * @param connect_delay The amount of time that the command should wait before + * we try to connect the deferred stream for the command. + * @param setup The stream setup callback for the command object. + * @param setup_ctx Arbitrary data passed to the `setup` callback. + * @param dbname The database name associated with the command. Required for OP_MSG + * @param cmd The BSON data that will be sent in the command message + * @param cmd_opcode The wire protocol opcode for the command + * @param cb A callback that is invoked during events associated with the command. + * @param userdata Arbitrary data pointer associated with the command object + * @param timeout A timeout for the command. @see _acmd_reset_elapsed + * @return mongoc_async_cmd_t* A newly created asynchronous command object + */ mongoc_async_cmd_t * mongoc_async_cmd_new (mongoc_async_t *async, mongoc_stream_t *stream, bool is_setup_done, struct addrinfo *dns_result, - mongoc_async_cmd_initiate_t initiator, - int64_t initiate_delay_ms, - mongoc_async_cmd_setup_t setup, + mongoc_async_cmd_connect_cb connect_callback, + mlib_duration connect_delay, + mongoc_async_cmd_stream_setup_cb setup, void *setup_ctx, const char *dbname, const bson_t *cmd, const int32_t cmd_opcode, - mongoc_async_cmd_cb_t cb, - void *cb_data, - int64_t timeout_msec); + mongoc_async_cmd_event_cb cb, + void *userdata, + mlib_duration timeout); + +/** + * @brief Obtain a deadline timer that will expire when the given async command + * will time out. + * + * Note that the initiation time of the command can be changed, which will also + * adjust the point-in-time at which it expires. + */ +static inline mlib_timer +_acmd_deadline (const mongoc_async_cmd_t *self) +{ + BSON_ASSERT_PARAM (self); + return mlib_expires_at (mlib_time_add (self->_start_time, self->_timeout)); +} + +/** + * @brief Determine whether the given async command object has timed out + */ +static inline bool +_acmd_has_timed_out (const mongoc_async_cmd_t *self) +{ + return mlib_timer_is_expired (_acmd_deadline (self)); +} + +/** + * @brief Cancel an in-progress command. + * + * This doesn't immediately destroy any resources or perform I/O, it just marks + * the command to abort the next time it is polled. + */ +static inline void +_acmd_cancel (mongoc_async_cmd_t *self) +{ + BSON_ASSERT_PARAM (self); + + // Don't attempt to cancel a comman in the error state, as it will already have + // a waiting completion. + if (self->state != MONGOC_ASYNC_CMD_ERROR_STATE) { + self->state = MONGOC_ASYNC_CMD_CANCELLED_STATE; + } +} + +/** + * @brief Adjust the connect-delay timer for an async command by the given duration + * + * @param d A duration to be added/removed to the command's connect wait. + * + * This only effects commands that don't have an open stream and are pending a + * connect. If this causes the connect-delay timer to expire, then the command + * will attempt to connect the next time it is polled. + */ +static inline void +_acmd_adjust_connect_delay (mongoc_async_cmd_t *self, const mlib_duration d) +{ + self->_connect_delay_timer.expires_at = mlib_time_add (self->_connect_delay_timer.expires_at, d); +} + +/** + * @brief Reset the elapsed time for the command, changing when it will timeout + * + * XXX: This is a HACK to fix CDRIVER-1571. The problem is that the provided deferred + * connect (_stream_setup and/or _stream_connect) callbacks can perform blocking + * I/O that delays everyone in the async pool, which can cause other commands + * to exceed their timeout because one operation is blocking the entire pool. + * + * This function has the side effect that a command can exceed its allotted timeout + * because this function is called multiple times, so only a single individual I/O + * operation can actually timeout, rather than the entire composed operation. + * + * The proper fix is to force `_stream_setup` and `_stream_connect` to be + * non-blocking, and the reference start time for the command can remain fixed. + */ +static inline void +_acmd_reset_elapsed (mongoc_async_cmd_t *self) +{ + self->_start_time = mlib_now (); +} + +/** + * @brief Obtain the amount of time that the command has been running + */ +static inline mlib_duration +_acmd_elapsed (mongoc_async_cmd_t const *self) +{ + return mlib_elapsed_since (self->_start_time); +} + +/** + * @brief Obtain the userdata pointer associated with the given async command + * object + * + * @param T The type to read from the pointer + * @param Command Pointer to a command object + */ +#define _acmd_userdata(T, Command) ((T *) ((Command)->_userdata)) void mongoc_async_cmd_destroy (mongoc_async_cmd_t *acmd); +/** + * @brief Pump the asynchronous command object state machine. + * + * If this function completes the command, it will destroy the async command + * object and return `false`. Otherwise, it will return `true`. + */ bool mongoc_async_cmd_run (mongoc_async_cmd_t *acmd); #ifdef MONGOC_ENABLE_SSL +/** + * @brief Stream setup callback. Initializes TLS on the stream before the command runner tries to use it. + * + * @param ctx The userdata for the TLS setup is the hostname string for the peer. + * + * Refer to `mongoc_async_cmd_stream_setup_cb` for signature details + */ int -mongoc_async_cmd_tls_setup (mongoc_stream_t *stream, int *events, void *ctx, int32_t timeout_msec, bson_error_t *error); +mongoc_async_cmd_tls_setup (mongoc_stream_t *stream, int *events, void *ctx, mlib_timer deadline, bson_error_t *error); #endif BSON_END_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-async-cmd.c b/src/libmongoc/src/mongoc/mongoc-async-cmd.c index f32cdc2e3e..eaeffe080a 100644 --- a/src/libmongoc/src/mongoc/mongoc-async-cmd.c +++ b/src/libmongoc/src/mongoc/mongoc-async-cmd.c @@ -30,7 +30,10 @@ #include +#include #include +#include +#include #ifdef MONGOC_ENABLE_SSL #include @@ -38,20 +41,20 @@ typedef mongoc_async_cmd_result_t (*_mongoc_async_cmd_phase_t) (mongoc_async_cmd_t *cmd); -mongoc_async_cmd_result_t -_mongoc_async_cmd_phase_initiate (mongoc_async_cmd_t *cmd); -mongoc_async_cmd_result_t -_mongoc_async_cmd_phase_setup (mongoc_async_cmd_t *cmd); -mongoc_async_cmd_result_t +static mongoc_async_cmd_result_t +_mongoc_async_cmd_phase_connect (mongoc_async_cmd_t *cmd); +static mongoc_async_cmd_result_t +_mongoc_async_cmd_phase_stream_setup (mongoc_async_cmd_t *cmd); +static mongoc_async_cmd_result_t _mongoc_async_cmd_phase_send (mongoc_async_cmd_t *cmd); -mongoc_async_cmd_result_t +static mongoc_async_cmd_result_t _mongoc_async_cmd_phase_recv_len (mongoc_async_cmd_t *cmd); -mongoc_async_cmd_result_t +static mongoc_async_cmd_result_t _mongoc_async_cmd_phase_recv_rpc (mongoc_async_cmd_t *cmd); static const _mongoc_async_cmd_phase_t gMongocCMDPhases[] = { - _mongoc_async_cmd_phase_initiate, - _mongoc_async_cmd_phase_setup, + _mongoc_async_cmd_phase_connect, + _mongoc_async_cmd_phase_stream_setup, _mongoc_async_cmd_phase_send, _mongoc_async_cmd_phase_recv_len, _mongoc_async_cmd_phase_recv_rpc, @@ -61,22 +64,24 @@ static const _mongoc_async_cmd_phase_t gMongocCMDPhases[] = { #ifdef MONGOC_ENABLE_SSL int -mongoc_async_cmd_tls_setup (mongoc_stream_t *stream, int *events, void *ctx, int32_t timeout_msec, bson_error_t *error) +mongoc_async_cmd_tls_setup (mongoc_stream_t *stream, int *events, void *ctx, mlib_timer deadline, bson_error_t *error) { mongoc_stream_t *tls_stream; const char *host = (const char *) ctx; int retry_events = 0; - for (tls_stream = stream; tls_stream->type != MONGOC_STREAM_TLS; tls_stream = mongoc_stream_get_base_stream (tls_stream)) { } -#if defined(MONGOC_ENABLE_SSL_OPENSSL) || defined(MONGOC_ENABLE_SSL_SECURE_CHANNEL) - /* pass 0 for the timeout to begin / continue non-blocking handshake */ - timeout_msec = 0; -#endif - if (mongoc_stream_tls_handshake (tls_stream, host, timeout_msec, &retry_events, error)) { + // Try to do a non-blocking operation, if our backend allows it + const mlib_duration_rep_t remain_ms = // + (MONGOC_SECURE_CHANNEL_ENABLED () || MONGOC_OPENSSL_ENABLED ()) + // Pass 0 for the timeout to begin / continue a non-blocking handshake + ? 0 + // Otherwise, use the deadline + : mlib_milliseconds_count (mlib_timer_remaining (deadline)); + if (mongoc_stream_tls_handshake (tls_stream, host, remain_ms, &retry_events, error)) { return 1; } @@ -91,39 +96,38 @@ mongoc_async_cmd_tls_setup (mongoc_stream_t *stream, int *events, void *ctx, int bool mongoc_async_cmd_run (mongoc_async_cmd_t *acmd) { - mongoc_async_cmd_result_t result; - int64_t duration_usec; - _mongoc_async_cmd_phase_t phase_callback; - - BSON_ASSERT (acmd); + BSON_ASSERT_PARAM (acmd); /* if we have successfully connected to the node, call the callback. */ if (acmd->state == MONGOC_ASYNC_CMD_SEND) { - acmd->cb (acmd, MONGOC_ASYNC_CMD_CONNECTED, NULL, 0); + acmd->_event_callback (acmd, MONGOC_ASYNC_CMD_CONNECTED, NULL, _acmd_elapsed (acmd)); } - phase_callback = gMongocCMDPhases[acmd->state]; - if (phase_callback) { - result = phase_callback (acmd); - } else { - result = MONGOC_ASYNC_CMD_ERROR; - } + _mongoc_async_cmd_phase_t const phase_callback = gMongocCMDPhases[acmd->state]; + mongoc_async_cmd_result_t const result = phase_callback // + ? phase_callback (acmd) + : MONGOC_ASYNC_CMD_ERROR; - if (result == MONGOC_ASYNC_CMD_IN_PROGRESS) { + switch (result) { + case MONGOC_ASYNC_CMD_IN_PROGRESS: + // No callback on progress events. Just return true to tell the caller + // that there's more work to do. return true; + case MONGOC_ASYNC_CMD_CONNECTED: + mlib_check (false, because, "We should not reach this state"); + abort (); + case MONGOC_ASYNC_CMD_SUCCESS: + case MONGOC_ASYNC_CMD_ERROR: + case MONGOC_ASYNC_CMD_TIMEOUT: + acmd->_event_callback (acmd, result, &acmd->_response_data, _acmd_elapsed (acmd)); + // No more work on this command. Destroy the object and tell the caller + // it's been removed + mongoc_async_cmd_destroy (acmd); + return false; + default: + mlib_check (false, because, "Invalid async command state"); + abort (); } - - duration_usec = bson_get_monotonic_time () - acmd->cmd_started; - - if (result == MONGOC_ASYNC_CMD_SUCCESS) { - acmd->cb (acmd, result, &acmd->reply, duration_usec); - } else { - /* we're in ERROR, TIMEOUT, or CANCELED */ - acmd->cb (acmd, result, NULL, duration_usec); - } - - mongoc_async_cmd_destroy (acmd); - return false; } static void @@ -144,12 +148,12 @@ _mongoc_async_cmd_init_send (const int32_t cmd_opcode, mongoc_async_cmd_t *acmd, message_length += mcd_rpc_op_query_set_full_collection_name (acmd->rpc, acmd->ns); message_length += mcd_rpc_op_query_set_number_to_skip (acmd->rpc, 0); message_length += mcd_rpc_op_query_set_number_to_return (acmd->rpc, -1); - message_length += mcd_rpc_op_query_set_query (acmd->rpc, bson_get_data (&acmd->cmd)); + message_length += mcd_rpc_op_query_set_query (acmd->rpc, bson_get_data (&acmd->_command)); } else { mcd_rpc_op_msg_set_sections_count (acmd->rpc, 1u); message_length += mcd_rpc_op_msg_set_flag_bits (acmd->rpc, MONGOC_OP_MSG_FLAG_NONE); message_length += mcd_rpc_op_msg_section_set_kind (acmd->rpc, 0u, 0); - message_length += mcd_rpc_op_msg_section_set_body (acmd->rpc, 0u, bson_get_data (&acmd->cmd)); + message_length += mcd_rpc_op_msg_section_set_body (acmd->rpc, 0u, bson_get_data (&acmd->_command)); } mcd_rpc_message_set_length (acmd->rpc, message_length); @@ -165,10 +169,13 @@ void _mongoc_async_cmd_state_start (mongoc_async_cmd_t *acmd, bool is_setup_done) { if (!acmd->stream) { - acmd->state = MONGOC_ASYNC_CMD_INITIATE; - } else if (acmd->setup && !is_setup_done) { - acmd->state = MONGOC_ASYNC_CMD_SETUP; + // No stream yet associated, so we need to initiate a new connection + acmd->state = MONGOC_ASYNC_CMD_PENDING_CONNECT; + } else if (acmd->_stream_setup && !is_setup_done) { + // We have a stream, and a setup callback, so call that setup callback next + acmd->state = MONGOC_ASYNC_CMD_STREAM_SETUP; } else { + // We have a stream, and no setup required. We're ready to send immediately. acmd->state = MONGOC_ASYNC_CMD_SEND; } @@ -180,37 +187,39 @@ mongoc_async_cmd_new (mongoc_async_t *async, mongoc_stream_t *stream, bool is_setup_done, struct addrinfo *dns_result, - mongoc_async_cmd_initiate_t initiator, - int64_t initiate_delay_ms, - mongoc_async_cmd_setup_t setup, - void *setup_ctx, + mongoc_async_cmd_connect_cb connect_cb, + mlib_duration connect_delay, + mongoc_async_cmd_stream_setup_cb stream_setup, + void *setup_userdata, const char *dbname, const bson_t *cmd, const int32_t cmd_opcode, /* OP_QUERY or OP_MSG */ - mongoc_async_cmd_cb_t cb, - void *cb_data, - int64_t timeout_msec) + mongoc_async_cmd_event_cb event_cb, + void *userdata, + mlib_duration timeout) { BSON_ASSERT_PARAM (cmd); BSON_ASSERT_PARAM (dbname); mongoc_async_cmd_t *const acmd = BSON_ALIGNED_ALLOC0 (mongoc_async_cmd_t); + acmd->_start_time = mlib_now (); + acmd->_connect_delay_timer = mlib_expires_at (mlib_time_add (acmd->_start_time, connect_delay)); acmd->async = async; acmd->dns_result = dns_result; - acmd->timeout_msec = timeout_msec; + acmd->_timeout = timeout; acmd->stream = stream; - acmd->initiator = initiator; - acmd->initiate_delay_ms = initiate_delay_ms; - acmd->setup = setup; - acmd->setup_ctx = setup_ctx; - acmd->cb = cb; - acmd->data = cb_data; - acmd->connect_started = bson_get_monotonic_time (); - bson_copy_to (cmd, &acmd->cmd); + acmd->_stream_connect = connect_cb; + acmd->_stream_setup = stream_setup; + acmd->_stream_setup_userdata = setup_userdata; + acmd->_event_callback = event_cb; + acmd->_userdata = userdata; + acmd->state = MONGOC_ASYNC_CMD_PENDING_CONNECT; + acmd->_response_data = (bson_t) BSON_INITIALIZER; + bson_copy_to (cmd, &acmd->_command); if (MONGOC_OP_CODE_MSG == cmd_opcode) { /* If we're sending an OP_MSG, we need to add the "db" field: */ - bson_append_utf8 (&acmd->cmd, "$db", 3, "admin", 5); + bson_append_utf8 (&acmd->_command, "$db", 3, "admin", 5); } acmd->rpc = mcd_rpc_message_new (); @@ -236,11 +245,8 @@ mongoc_async_cmd_destroy (mongoc_async_cmd_t *acmd) DL_DELETE (acmd->async->cmds, acmd); acmd->async->ncmds--; - bson_destroy (&acmd->cmd); - - if (acmd->reply_needs_cleanup) { - bson_destroy (&acmd->reply); - } + bson_destroy (&acmd->_command); + bson_destroy (&acmd->_response_data); bson_free (acmd->iovec); _mongoc_buffer_destroy (&acmd->buffer); @@ -251,29 +257,29 @@ mongoc_async_cmd_destroy (mongoc_async_cmd_t *acmd) } mongoc_async_cmd_result_t -_mongoc_async_cmd_phase_initiate (mongoc_async_cmd_t *acmd) +_mongoc_async_cmd_phase_connect (mongoc_async_cmd_t *acmd) { - acmd->stream = acmd->initiator (acmd); + acmd->stream = acmd->_stream_connect (acmd); if (!acmd->stream) { return MONGOC_ASYNC_CMD_ERROR; } - /* reset the connect started time after connection starts. */ - acmd->connect_started = bson_get_monotonic_time (); - if (acmd->setup) { - acmd->state = MONGOC_ASYNC_CMD_SETUP; + + _acmd_reset_elapsed (acmd); + if (acmd->_stream_setup) { + // There is a setup callback that we need to call + acmd->state = MONGOC_ASYNC_CMD_STREAM_SETUP; } else { + // There is no setup callback, so we can send data immediately acmd->state = MONGOC_ASYNC_CMD_SEND; } return MONGOC_ASYNC_CMD_IN_PROGRESS; } -mongoc_async_cmd_result_t -_mongoc_async_cmd_phase_setup (mongoc_async_cmd_t *acmd) +static mongoc_async_cmd_result_t +_mongoc_async_cmd_phase_stream_setup (mongoc_async_cmd_t *acmd) { - int retval; - - BSON_ASSERT (acmd->timeout_msec < INT32_MAX); - retval = acmd->setup (acmd->stream, &acmd->events, acmd->setup_ctx, (int32_t) acmd->timeout_msec, &acmd->error); + int const retval = acmd->_stream_setup ( + acmd->stream, &acmd->events, acmd->_stream_setup_userdata, _acmd_deadline (acmd), &acmd->error); switch (retval) { case -1: return MONGOC_ASYNC_CMD_ERROR; @@ -358,7 +364,7 @@ _mongoc_async_cmd_phase_send (mongoc_async_cmd_t *acmd) acmd->bytes_to_read = 4; acmd->events = POLLIN; - acmd->cmd_started = bson_get_monotonic_time (); + _acmd_reset_elapsed (acmd); return MONGOC_ASYNC_CMD_IN_PROGRESS; } @@ -449,14 +455,12 @@ _mongoc_async_cmd_phase_recv_rpc (mongoc_async_cmd_t *acmd) _mongoc_buffer_init (&acmd->buffer, decompressed_data, decompressed_data_len, NULL, NULL); } - if (!mcd_rpc_message_get_body (acmd->rpc, &acmd->reply)) { + if (!mcd_rpc_message_get_body (acmd->rpc, &acmd->_response_data)) { _mongoc_set_error ( &acmd->error, MONGOC_ERROR_PROTOCOL, MONGOC_ERROR_PROTOCOL_INVALID_REPLY, "Invalid reply from server"); return MONGOC_ASYNC_CMD_ERROR; } - acmd->reply_needs_cleanup = true; - return MONGOC_ASYNC_CMD_SUCCESS; } diff --git a/src/libmongoc/src/mongoc/mongoc-async-private.h b/src/libmongoc/src/mongoc/mongoc-async-private.h index 3a225f16f2..c1cbe40717 100644 --- a/src/libmongoc/src/mongoc/mongoc-async-private.h +++ b/src/libmongoc/src/mongoc/mongoc-async-private.h @@ -14,15 +14,19 @@ * limitations under the License. */ -#include #ifndef MONGOC_ASYNC_PRIVATE_H #define MONGOC_ASYNC_PRIVATE_H +#include + #include #include +#include +#include + BSON_BEGIN_DECLS struct _mongoc_async_cmd; @@ -33,25 +37,6 @@ typedef struct _mongoc_async { uint32_t request_id; } mongoc_async_t; -typedef enum { - MONGOC_ASYNC_CMD_CONNECTED, - MONGOC_ASYNC_CMD_IN_PROGRESS, - MONGOC_ASYNC_CMD_SUCCESS, - MONGOC_ASYNC_CMD_ERROR, - MONGOC_ASYNC_CMD_TIMEOUT, -} mongoc_async_cmd_result_t; - -typedef void (*mongoc_async_cmd_cb_t) (struct _mongoc_async_cmd *acmd, - mongoc_async_cmd_result_t result, - const bson_t *bson, - int64_t duration_usec); - -typedef mongoc_stream_t *(*mongoc_async_cmd_initiate_t) (struct _mongoc_async_cmd *); - -typedef int (*mongoc_async_cmd_setup_t) ( - mongoc_stream_t *stream, int *events, void *ctx, int32_t timeout_msec, bson_error_t *error); - - mongoc_async_t * mongoc_async_new (void); diff --git a/src/libmongoc/src/mongoc/mongoc-async.c b/src/libmongoc/src/mongoc/mongoc-async.c index b42f8feead..52edebe8f3 100644 --- a/src/libmongoc/src/mongoc/mongoc-async.c +++ b/src/libmongoc/src/mongoc/mongoc-async.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,10 @@ #include +#include +#include +#include + mongoc_async_t * mongoc_async_new (void) @@ -54,20 +59,13 @@ mongoc_async_run (mongoc_async_t *async) mongoc_async_cmd_t *acmd, *tmp; mongoc_async_cmd_t **acmds_polled = NULL; mongoc_stream_poll_t *poller = NULL; - int nstreams, i; ssize_t nactive = 0; - int64_t now; - int64_t expire_at; - int64_t poll_timeout_msec; - size_t poll_size; - - now = bson_get_monotonic_time (); - poll_size = 0; + size_t poll_size = 0; - /* CDRIVER-1571 reset start times in case a stream initiator was slow */ DL_FOREACH (async->cmds, acmd) { - acmd->connect_started = now; + // CDRIVER-1571: See _acmd_reset_elapsed doc comment to explain this hack + _acmd_reset_elapsed (acmd); } while (async->ncmds) { @@ -78,25 +76,32 @@ mongoc_async_run (mongoc_async_t *async) poll_size = async->ncmds; } - expire_at = INT64_MAX; - nstreams = 0; + // Number of streams in the poller object + unsigned nstreams = 0; + + // The timer to wake up the poll() + mlib_timer poll_timer = mlib_expires_never (); /* check if any cmds are ready to be initiated. */ DL_FOREACH_SAFE (async->cmds, acmd, tmp) { - if (acmd->state == MONGOC_ASYNC_CMD_INITIATE) { + if (acmd->state == MONGOC_ASYNC_CMD_PENDING_CONNECT) { + // Command is waiting to be initiated. + // Timer for when the command should be initiated: + // Should not yet have an associated stream BSON_ASSERT (!acmd->stream); - if (now >= acmd->initiate_delay_ms * 1000 + acmd->connect_started) { + if (mlib_timer_is_expired (acmd->_connect_delay_timer)) { /* time to initiate. */ if (mongoc_async_cmd_run (acmd)) { + // We should now have an associated stream BSON_ASSERT (acmd->stream); } else { /* this command was removed. */ continue; } } else { - /* don't poll longer than the earliest cmd ready to init. */ - expire_at = BSON_MIN (expire_at, acmd->connect_started + acmd->initiate_delay_ms); + // Wake up poll() when the initiation timeout is hit + poll_timer = mlib_soonest_timer (poll_timer, acmd->_connect_delay_timer); } } @@ -105,7 +110,8 @@ mongoc_async_run (mongoc_async_t *async) poller[nstreams].stream = acmd->stream; poller[nstreams].events = acmd->events; poller[nstreams].revents = 0; - expire_at = BSON_MIN (expire_at, acmd->connect_started + acmd->timeout_msec * 1000); + // Wake up poll() if the object's overall timeout is hit + poll_timer = mlib_soonest_timer (poll_timer, _acmd_deadline (acmd)); ++nstreams; } } @@ -115,21 +121,18 @@ mongoc_async_run (mongoc_async_t *async) break; } - poll_timeout_msec = BSON_MAX (0, (expire_at - now) / 1000); - BSON_ASSERT (poll_timeout_msec < INT32_MAX); - if (nstreams > 0) { /* we need at least one stream to poll. */ - nactive = mongoc_stream_poll (poller, nstreams, (int32_t) poll_timeout_msec); + nactive = _mongoc_stream_poll_internal (poller, nstreams, poll_timer); } else { /* currently this does not get hit. we always have at least one command * initialized with a stream. */ - _mongoc_usleep (poll_timeout_msec * 1000); + mlib_sleep_until (poll_timer.expires_at); } if (nactive > 0) { - for (i = 0; i < nstreams; i++) { - mongoc_async_cmd_t *iter = acmds_polled[i]; + mlib_foreach_urange (i, nstreams) { + mongoc_async_cmd_t *const iter = acmds_polled[i]; if (poller[i].revents & (POLLERR | POLLHUP)) { int hup = poller[i].revents & POLLHUP; if (iter->state == MONGOC_ASYNC_CMD_SEND) { @@ -161,25 +164,23 @@ mongoc_async_run (mongoc_async_t *async) DL_FOREACH_SAFE (async->cmds, acmd, tmp) { /* check if an initiated cmd has passed the connection timeout. */ - if (acmd->state != MONGOC_ASYNC_CMD_INITIATE && now > acmd->connect_started + acmd->timeout_msec * 1000) { + if (acmd->state != MONGOC_ASYNC_CMD_PENDING_CONNECT && _acmd_has_timed_out (acmd)) { _mongoc_set_error (&acmd->error, MONGOC_ERROR_STREAM, MONGOC_ERROR_STREAM_CONNECT, acmd->state == MONGOC_ASYNC_CMD_SEND ? "connection timeout" : "socket timeout"); - acmd->cb (acmd, MONGOC_ASYNC_CMD_TIMEOUT, NULL, (now - acmd->connect_started) / 1000); + acmd->_event_callback (acmd, MONGOC_ASYNC_CMD_TIMEOUT, NULL, _acmd_elapsed (acmd)); /* Remove acmd from the async->cmds doubly-linked list */ mongoc_async_cmd_destroy (acmd); - } else if (acmd->state == MONGOC_ASYNC_CMD_CANCELED_STATE) { - acmd->cb (acmd, MONGOC_ASYNC_CMD_ERROR, NULL, (now - acmd->connect_started) / 1000); + } else if (acmd->state == MONGOC_ASYNC_CMD_CANCELLED_STATE) { + acmd->_event_callback (acmd, MONGOC_ASYNC_CMD_ERROR, NULL, _acmd_elapsed (acmd)); /* Remove acmd from the async->cmds doubly-linked list */ mongoc_async_cmd_destroy (acmd); } } - - now = bson_get_monotonic_time (); } bson_free (poller); diff --git a/src/libmongoc/src/mongoc/mongoc-client-pool.c b/src/libmongoc/src/mongoc/mongoc-client-pool.c index 75ff559a68..a0f6dfc12a 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-pool.c +++ b/src/libmongoc/src/mongoc/mongoc-client-pool.c @@ -33,6 +33,10 @@ #include +#include +#include +#include + #ifdef MONGOC_ENABLE_SSL #include #endif @@ -312,18 +316,17 @@ mongoc_client_t * mongoc_client_pool_pop (mongoc_client_pool_t *pool) { mongoc_client_t *client; - int32_t wait_queue_timeout_ms; - int64_t expire_at_ms = -1; - int64_t now_ms; int r; ENTRY; BSON_ASSERT_PARAM (pool); - wait_queue_timeout_ms = mongoc_uri_get_option_as_int32 (pool->uri, MONGOC_URI_WAITQUEUETIMEOUTMS, -1); + mlib_timer expires_at = mlib_expires_never (); + + const int32_t wait_queue_timeout_ms = mongoc_uri_get_option_as_int32 (pool->uri, MONGOC_URI_WAITQUEUETIMEOUTMS, -1); if (wait_queue_timeout_ms > 0) { - expire_at_ms = (bson_get_monotonic_time () / 1000) + wait_queue_timeout_ms; + expires_at = mlib_expires_after (wait_queue_timeout_ms, ms); } bson_mutex_lock (&pool->mutex); @@ -336,9 +339,9 @@ mongoc_client_pool_pop (mongoc_client_pool_t *pool) pool->size++; } else { if (wait_queue_timeout_ms > 0) { - now_ms = bson_get_monotonic_time () / 1000; - if (now_ms < expire_at_ms) { - r = mongoc_cond_timedwait (&pool->cond, &pool->mutex, expire_at_ms - now_ms); + if (!mlib_timer_is_expired (expires_at)) { + const mlib_duration remain = mlib_timer_remaining (expires_at); + r = mongoc_cond_timedwait (&pool->cond, &pool->mutex, mlib_milliseconds_count (remain)); if (mongo_cond_ret_is_timedout (r)) { GOTO (done); } diff --git a/src/libmongoc/src/mongoc/mongoc-client-session.c b/src/libmongoc/src/mongoc/mongoc-client-session.c index 1277af16ba..0727962c81 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-session.c +++ b/src/libmongoc/src/mongoc/mongoc-client-session.c @@ -26,6 +26,9 @@ #include #include +#include +#include + #define WITH_TXN_TIMEOUT_MS (120 * 1000) static void @@ -1174,7 +1177,7 @@ mongoc_client_session_commit_transaction (mongoc_client_session_t *session, bson /* Waste the test timeout, if there is one set. */ if (session->with_txn_timeout_ms) { - _mongoc_usleep (session->with_txn_timeout_ms * 1000); + mlib_sleep_for (session->with_txn_timeout_ms, ms); } RETURN (r); diff --git a/src/libmongoc/src/mongoc/mongoc-client.c b/src/libmongoc/src/mongoc/mongoc-client.c index 23bf4a5e4c..ee81a2dc2c 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.c +++ b/src/libmongoc/src/mongoc/mongoc-client.c @@ -17,6 +17,7 @@ #include #include + #ifdef MONGOC_HAVE_DNSAPI /* for DnsQuery_UTF8 */ #include diff --git a/src/libmongoc/src/mongoc/mongoc-config.h.in b/src/libmongoc/src/mongoc/mongoc-config.h.in index de8d2a5975..fffb392f6f 100644 --- a/src/libmongoc/src/mongoc/mongoc-config.h.in +++ b/src/libmongoc/src/mongoc/mongoc-config.h.in @@ -48,8 +48,8 @@ * compiled with Native SSL support on Windows */ #define MONGOC_ENABLE_SSL_SECURE_CHANNEL @MONGOC_ENABLE_SSL_SECURE_CHANNEL@ - -#if MONGOC_ENABLE_SSL_SECURE_CHANNEL != 1 +#define MONGOC_SECURE_CHANNEL_ENABLED() @MONGOC_ENABLE_SSL_SECURE_CHANNEL@ +#if MONGOC_SECURE_CHANNEL_ENABLED() != 1 # undef MONGOC_ENABLE_SSL_SECURE_CHANNEL #endif @@ -65,8 +65,8 @@ #endif /* - * MONGOC_HAVE_BCRYPT_PBKDF2 is set from configure to determine if - * our Bcrypt Windows library supports PBKDF2 + * MONGOC_HAVE_BCRYPT_PBKDF2 is set from configure to determine if + * our Bcrypt Windows library supports PBKDF2 */ #define MONGOC_HAVE_BCRYPT_PBKDF2 @MONGOC_HAVE_BCRYPT_PBKDF2@ @@ -101,8 +101,8 @@ * compiled with OpenSSL support. */ #define MONGOC_ENABLE_SSL_OPENSSL @MONGOC_ENABLE_SSL_OPENSSL@ - -#if MONGOC_ENABLE_SSL_OPENSSL != 1 +#define MONGOC_OPENSSL_ENABLED() @MONGOC_ENABLE_SSL_OPENSSL@ +#if MONGOC_OPENSSL_ENABLED() != 1 # undef MONGOC_ENABLE_SSL_OPENSSL #endif diff --git a/src/libmongoc/src/mongoc/mongoc-crypt.c b/src/libmongoc/src/mongoc/mongoc-crypt.c index a884bf6713..fb4fa192e7 100644 --- a/src/libmongoc/src/mongoc/mongoc-crypt.c +++ b/src/libmongoc/src/mongoc/mongoc-crypt.c @@ -36,6 +36,7 @@ #include #include +#include #include @@ -586,9 +587,7 @@ _state_need_kms (_state_machine_t *state_machine, bson_error_t *error) } sleep_usec = mongocrypt_kms_ctx_usleep (kms_ctx); - if (sleep_usec > 0) { - _mongoc_usleep (sleep_usec); - } + mlib_sleep_for (sleep_usec, us); mongoc_stream_destroy (tls_stream); tls_stream = _get_stream (endpoint, sockettimeout, ssl_opt, error); diff --git a/src/libmongoc/src/mongoc/mongoc-stream-private.h b/src/libmongoc/src/mongoc/mongoc-stream-private.h index f39e360388..3a2b9e92ae 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-private.h +++ b/src/libmongoc/src/mongoc/mongoc-stream-private.h @@ -22,6 +22,8 @@ #include #include +#include + BSON_BEGIN_DECLS @@ -44,6 +46,16 @@ _mongoc_stream_writev_full ( mongoc_stream_t * mongoc_stream_get_root_stream (mongoc_stream_t *stream); +/** + * @brief Poll the given set of streams + * + * @param streams Pointer to an array of stream polling parameters + * @param nstreams The number of streams in the array + * @param until A timer that will wake up `poll()` from blocking + */ +ssize_t +_mongoc_stream_poll_internal (mongoc_stream_poll_t *streams, size_t nstreams, mlib_timer until); + BSON_END_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-stream.c b/src/libmongoc/src/mongoc/mongoc-stream.c index 576445e362..3de1d3d7bf 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.c +++ b/src/libmongoc/src/mongoc/mongoc-stream.c @@ -33,6 +33,8 @@ #include #include +#include +#include #undef MONGOC_LOG_DOMAIN @@ -326,7 +328,13 @@ mongoc_stream_get_tls_stream (mongoc_stream_t *stream) /* IN */ } ssize_t -mongoc_stream_poll (mongoc_stream_poll_t *streams, size_t nstreams, int32_t timeout) +mongoc_stream_poll (mongoc_stream_poll_t *streams, size_t nstreams, int32_t timeout_ms) +{ + return _mongoc_stream_poll_internal (streams, nstreams, mlib_expires_after (mlib_duration (timeout_ms, ms))); +} + +ssize_t +_mongoc_stream_poll_internal (mongoc_stream_poll_t *streams, size_t nstreams, mlib_timer until) { mongoc_stream_poll_t *poller = (mongoc_stream_poll_t *) bson_malloc (sizeof (*poller) * nstreams); @@ -353,7 +361,12 @@ mongoc_stream_poll (mongoc_stream_poll_t *streams, size_t nstreams, int32_t time goto CLEANUP; } - rval = poller[0].stream->poll (poller, nstreams, timeout); + int32_t time_remain_ms = 0; + if (mlib_narrow (&time_remain_ms, mlib_milliseconds_count (mlib_timer_remaining (until)))) { + // Too many ms, just use the max + time_remain_ms = INT32_MAX; + } + rval = poller[0].stream->poll (poller, nstreams, time_remain_ms); if (rval > 0) { for (size_t i = 0u; i < nstreams; i++) { diff --git a/src/libmongoc/src/mongoc/mongoc-stream.h b/src/libmongoc/src/mongoc/mongoc-stream.h index 4bfe7cef98..669a6f3928 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream.h +++ b/src/libmongoc/src/mongoc/mongoc-stream.h @@ -83,8 +83,17 @@ MONGOC_EXPORT (bool) mongoc_stream_timed_out (mongoc_stream_t *stream); MONGOC_EXPORT (bool) mongoc_stream_should_retry (mongoc_stream_t *stream); + +/** + * @brief Poll a set of streams + * + * @param streams Pointer to an array of streams to be polled + * @param nstreams The number of streams in the array pointed-to by `streams` + * @param timeout_ms The maximum number of milliseconds to poll + * + */ MONGOC_EXPORT (ssize_t) -mongoc_stream_poll (mongoc_stream_poll_t *streams, size_t nstreams, int32_t timeout); +mongoc_stream_poll (mongoc_stream_poll_t *streams, size_t nstreams, int32_t timeout_ms); BSON_END_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-topology-scanner-private.h b/src/libmongoc/src/mongoc/mongoc-topology-scanner-private.h index f8854cb23d..55c7f6a63c 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-scanner-private.h +++ b/src/libmongoc/src/mongoc/mongoc-topology-scanner-private.h @@ -124,7 +124,7 @@ typedef struct mongoc_topology_scanner { mongoc_topology_scanner_cb_t cb; void *cb_data; const mongoc_uri_t *uri; - mongoc_async_cmd_setup_t setup; + mongoc_async_cmd_stream_setup_cb setup; mongoc_stream_initiator_t initiator; void *initiator_context; bson_error_t error; diff --git a/src/libmongoc/src/mongoc/mongoc-topology-scanner.c b/src/libmongoc/src/mongoc/mongoc-topology-scanner.c index 3007e223a7..79887b2ea4 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-scanner.c +++ b/src/libmongoc/src/mongoc/mongoc-topology-scanner.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -26,6 +27,9 @@ #include +#include +#include + #ifdef MONGOC_ENABLE_SSL #include #endif @@ -62,23 +66,23 @@ #define MONGOC_LOG_DOMAIN "topology_scanner" #define DNS_CACHE_TIMEOUT_MS 10 * 60 * 1000 -#define HAPPY_EYEBALLS_DELAY_MS 250 +#define HAPPY_EYEBALLS_DELAY mlib_duration (250, ms) /* forward declarations */ static void _async_connected (mongoc_async_cmd_t *acmd); static void -_async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, int64_t duration_usec); +_async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, mlib_duration elapsed); static void -_async_error_or_timeout (mongoc_async_cmd_t *acmd, int64_t duration_usec, const char *default_err_msg); +_async_error_or_timeout (mongoc_async_cmd_t *acmd, mlib_duration elapsed, const char *default_err_msg); static void _async_handler (mongoc_async_cmd_t *acmd, mongoc_async_cmd_result_t async_status, const bson_t *hello_response, - int64_t duration_usec); + mlib_duration duration); static void _mongoc_topology_scanner_monitor_heartbeat_started (const mongoc_topology_scanner_t *ts, @@ -88,19 +92,34 @@ static void _mongoc_topology_scanner_monitor_heartbeat_succeeded (const mongoc_topology_scanner_t *ts, const mongoc_host_list_t *host, const bson_t *reply, - int64_t duration_usec); + mlib_duration elapsed); static void _mongoc_topology_scanner_monitor_heartbeat_failed (const mongoc_topology_scanner_t *ts, const mongoc_host_list_t *host, const bson_error_t *error, - int64_t duration_usec); + mlib_duration elapsed); /* reset "retired" nodes that failed or were removed in the previous scan */ static void _delete_retired_nodes (mongoc_topology_scanner_t *ts); +// Get the scanner node associated with an async command +static mongoc_topology_scanner_node_t * +_scanner_node_of (mongoc_async_cmd_t const *a) +{ + return _acmd_userdata (mongoc_topology_scanner_node_t, a); +} + +// Test whether two async commands are associated with the same topology scanner node, +// and aren't the same command object +static bool +_is_sibling_command (mongoc_async_cmd_t const *l, mongoc_async_cmd_t const *r) +{ + return l != r && _scanner_node_of (l) == _scanner_node_of (r); +} + /* cancel any pending async commands for a specific node excluding acmd. * If acmd is NULL, cancel all async commands on the node. */ static void @@ -110,9 +129,23 @@ _cancel_commands_excluding (mongoc_topology_scanner_node_t *node, mongoc_async_c static int _count_acmds (mongoc_topology_scanner_node_t *node); -/* if acmd fails, schedule the sibling commands sooner. */ + +/** + * @brief Cause all sibling commands to initiate sooner + */ static void -_jumpstart_other_acmds (mongoc_topology_scanner_node_t *node, mongoc_async_cmd_t *acmd); +_jumpstart_other_acmds (mongoc_async_cmd_t const *const self) +{ + mongoc_async_cmd_t *other; + DL_FOREACH (self->async->cmds, other) + { + // Only consider commands on the same node + if (_is_sibling_command (self, other)) { + // Decrease the delay by the happy eyeballs duration. + _acmd_adjust_connect_delay (other, mlib_duration ((0, us), minus, HAPPY_EYEBALLS_DELAY)); + } + } +} static void _add_hello (mongoc_topology_scanner_t *ts) @@ -359,7 +392,7 @@ _begin_hello_cmd (mongoc_topology_scanner_node_t *node, mongoc_stream_t *stream, bool is_setup_done, struct addrinfo *dns_result, - int64_t initiate_delay_ms, + mlib_duration initiate_delay, bool use_handshake) { mongoc_topology_scanner_t *ts = node->ts; @@ -397,7 +430,7 @@ _begin_hello_cmd (mongoc_topology_scanner_node_t *node, is_setup_done, dns_result, _mongoc_topology_scanner_tcp_initiate, - initiate_delay_ms, + initiate_delay, ts->setup, node->host.host, "admin", @@ -405,7 +438,7 @@ _begin_hello_cmd (mongoc_topology_scanner_node_t *node, cmd_opcode, &_async_handler, node, - ts->connect_timeout_msec); + mlib_duration (ts->connect_timeout_msec, ms)); bson_destroy (&cmd); } @@ -651,18 +684,17 @@ mongoc_topology_scanner_has_node_for_host (mongoc_topology_scanner_t *ts, mongoc static void _async_connected (mongoc_async_cmd_t *acmd) { - mongoc_topology_scanner_node_t *node = (mongoc_topology_scanner_node_t *) acmd->data; + mongoc_topology_scanner_node_t *const node = _scanner_node_of (acmd); /* this cmd connected successfully, cancel other cmds on this node. */ _cancel_commands_excluding (node, acmd); node->successful_dns_result = acmd->dns_result; } static void -_async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, int64_t duration_usec) +_async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, mlib_duration elapsed) { - void *data = acmd->data; - mongoc_topology_scanner_node_t *node = (mongoc_topology_scanner_node_t *) data; - mongoc_stream_t *stream = acmd->stream; + mongoc_topology_scanner_node_t *const node = _scanner_node_of (acmd); + mongoc_stream_t *const stream = acmd->stream; mongoc_topology_scanner_t *ts = node->ts; if (node->retired) { @@ -675,7 +707,7 @@ _async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, int64_t node->last_used = bson_get_monotonic_time (); node->last_failed = -1; - _mongoc_topology_scanner_monitor_heartbeat_succeeded (ts, &node->host, hello_response, duration_usec); + _mongoc_topology_scanner_monitor_heartbeat_succeeded (ts, &node->host, hello_response, elapsed); /* set our successful stream. */ BSON_ASSERT (!node->stream); @@ -686,7 +718,7 @@ _async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, int64_t /* Store a server description associated with the handshake. */ mongoc_server_description_init (&sd, node->host.host_and_port, node->id); - mongoc_server_description_handle_hello (&sd, hello_response, duration_usec / 1000, &acmd->error); + mongoc_server_description_handle_hello (&sd, hello_response, mlib_milliseconds_count (elapsed), &acmd->error); node->handshake_sd = mongoc_server_description_new_copy (&sd); mongoc_server_description_cleanup (&sd); } @@ -700,14 +732,13 @@ _async_success (mongoc_async_cmd_t *acmd, const bson_t *hello_response, int64_t } /* mongoc_topology_scanner_cb_t takes rtt_msec, not usec */ - ts->cb (node->id, hello_response, duration_usec / 1000, ts->cb_data, &acmd->error); + ts->cb (node->id, hello_response, mlib_milliseconds_count (elapsed), ts->cb_data, &acmd->error); } static void -_async_error_or_timeout (mongoc_async_cmd_t *acmd, int64_t duration_usec, const char *default_err_msg) +_async_error_or_timeout (mongoc_async_cmd_t *acmd, mlib_duration elapsed, const char *default_err_msg) { - void *data = acmd->data; - mongoc_topology_scanner_node_t *node = (mongoc_topology_scanner_node_t *) data; + mongoc_topology_scanner_node_t *const node = _scanner_node_of (acmd); mongoc_stream_t *stream = acmd->stream; mongoc_topology_scanner_t *ts = node->ts; bson_error_t *error = &acmd->error; @@ -748,18 +779,18 @@ _async_error_or_timeout (mongoc_async_cmd_t *acmd, int64_t duration_usec, const message, node->host.host_and_port); - _mongoc_topology_scanner_monitor_heartbeat_failed (ts, &node->host, &node->last_error, duration_usec); + _mongoc_topology_scanner_monitor_heartbeat_failed (ts, &node->host, &node->last_error, elapsed); /* call the topology scanner callback. cannot connect to this node. * callback takes rtt_msec, not usec. */ - ts->cb (node->id, NULL, duration_usec / 1000, ts->cb_data, error); + ts->cb (node->id, NULL, mlib_milliseconds_count (elapsed), ts->cb_data, error); mongoc_server_description_destroy (node->handshake_sd); node->handshake_sd = NULL; } else { /* there are still more commands left for this node or it succeeded * with another stream. skip the topology scanner callback. */ - _jumpstart_other_acmds (node, acmd); + _jumpstart_other_acmds (acmd); } } @@ -776,22 +807,20 @@ static void _async_handler (mongoc_async_cmd_t *acmd, mongoc_async_cmd_result_t async_status, const bson_t *hello_response, - int64_t duration_usec) + mlib_duration duration) { - BSON_ASSERT (acmd->data); - switch (async_status) { case MONGOC_ASYNC_CMD_CONNECTED: _async_connected (acmd); return; case MONGOC_ASYNC_CMD_SUCCESS: - _async_success (acmd, hello_response, duration_usec); + _async_success (acmd, hello_response, duration); return; case MONGOC_ASYNC_CMD_TIMEOUT: - _async_error_or_timeout (acmd, duration_usec, "connection timeout"); + _async_error_or_timeout (acmd, duration, "connection timeout"); return; case MONGOC_ASYNC_CMD_ERROR: - _async_error_or_timeout (acmd, duration_usec, "connection error"); + _async_error_or_timeout (acmd, duration, "connection error"); return; case MONGOC_ASYNC_CMD_IN_PROGRESS: default: @@ -836,7 +865,7 @@ _mongoc_topology_scanner_node_setup_stream_for_tls (mongoc_topology_scanner_node mongoc_stream_t * _mongoc_topology_scanner_tcp_initiate (mongoc_async_cmd_t *acmd) { - mongoc_topology_scanner_node_t *node = (mongoc_topology_scanner_node_t *) acmd->data; + mongoc_topology_scanner_node_t *const node = _scanner_node_of (acmd); struct addrinfo *res = acmd->dns_result; mongoc_socket_t *sock = NULL; @@ -871,7 +900,7 @@ mongoc_topology_scanner_node_setup_tcp (mongoc_topology_scanner_node_t *node, bs char portstr[8]; mongoc_host_list_t *host; int s; - int64_t delay = 0; + mlib_duration delay = mlib_duration (); int64_t now = bson_get_monotonic_time (); ENTRY; @@ -914,14 +943,14 @@ mongoc_topology_scanner_node_setup_tcp (mongoc_topology_scanner_node_t *node, bs NULL /* stream */, false /* is_setup_done */, node->successful_dns_result, - 0 /* initiate_delay_ms */, + mlib_duration () /* initiate_delay */, true /* use_handshake */); } else { LL_FOREACH2 (node->dns_results, iter, ai_next) { _begin_hello_cmd (node, NULL /* stream */, false /* is_setup_done */, iter, delay, true /* use_handshake */); /* each subsequent DNS result will have an additional 250ms delay. */ - delay += HAPPY_EYEBALLS_DELAY_MS; + delay = mlib_duration (delay, plus, HAPPY_EYEBALLS_DELAY); } } @@ -982,8 +1011,12 @@ mongoc_topology_scanner_node_connect_unix (mongoc_topology_scanner_node_t *node, stream = _mongoc_topology_scanner_node_setup_stream_for_tls (node, mongoc_stream_socket_new (sock)); if (stream) { - _begin_hello_cmd ( - node, stream, false /* is_setup_done */, NULL /* dns result */, 0 /* delay */, true /* use_handshake */); + _begin_hello_cmd (node, + stream, + false /* is_setup_done */, + NULL /* dns result */, + mlib_duration () /* no delay */, + true /* use_handshake */); RETURN (true); } _mongoc_set_error (error, MONGOC_ERROR_STREAM, MONGOC_ERROR_STREAM_CONNECT, "Failed to create TLS stream"); @@ -1010,10 +1043,9 @@ mongoc_topology_scanner_node_setup (mongoc_topology_scanner_node_t *node, bson_e { bool success = false; mongoc_stream_t *stream; - int64_t start; _mongoc_topology_scanner_monitor_heartbeat_started (node->ts, &node->host); - start = bson_get_monotonic_time (); + const mlib_time_point start_time = mlib_now (); /* if there is already a working stream, push it back to be re-scanned. */ if (node->stream) { @@ -1021,7 +1053,7 @@ mongoc_topology_scanner_node_setup (mongoc_topology_scanner_node_t *node, bson_e node->stream, true /* is_setup_done */, NULL /* dns_result */, - 0 /* initiate_delay_ms */, + mlib_duration () /* initiate_delay */, false /* use_handshake */); node->stream = NULL; return; @@ -1050,7 +1082,7 @@ mongoc_topology_scanner_node_setup (mongoc_topology_scanner_node_t *node, bson_e stream, false /* is_setup_done */, NULL /* dns_result */, - 0 /* initiate_delay_ms */, + mlib_duration () /* initiate_delay */, true /* use_handshake */); } } else { @@ -1062,8 +1094,7 @@ mongoc_topology_scanner_node_setup (mongoc_topology_scanner_node_t *node, bson_e } if (!success) { - _mongoc_topology_scanner_monitor_heartbeat_failed ( - node->ts, &node->host, error, (bson_get_monotonic_time () - start) / 1000); + _mongoc_topology_scanner_monitor_heartbeat_failed (node->ts, &node->host, error, mlib_elapsed_since (start_time)); node->ts->setup_err_cb (node->id, node->ts->cb_data, error); return; @@ -1318,7 +1349,7 @@ static void _mongoc_topology_scanner_monitor_heartbeat_succeeded (const mongoc_topology_scanner_t *ts, const mongoc_host_list_t *host, const bson_t *reply, - int64_t duration_usec) + mlib_duration elapsed) { /* This redaction is more lenient than the general command redaction in the Command Logging and Monitoring spec and * the cmd*() structured log items. In those general command logs, sensitive replies are omitted entirely. In this @@ -1338,7 +1369,7 @@ _mongoc_topology_scanner_monitor_heartbeat_succeeded (const mongoc_topology_scan utf8 ("serverHost", host->host), int32 ("serverPort", host->port), boolean ("awaited", false), - monotonic_time_duration (duration_usec), + monotonic_time_duration (mlib_microseconds_count (elapsed)), bson_as_json ("reply", &hello_redacted)); if (ts->log_and_monitor->apm_callbacks.server_heartbeat_succeeded) { @@ -1346,7 +1377,7 @@ _mongoc_topology_scanner_monitor_heartbeat_succeeded (const mongoc_topology_scan event.host = host; event.context = ts->log_and_monitor->apm_context; event.reply = reply; - event.duration_usec = duration_usec; + event.duration_usec = mlib_microseconds_count (elapsed); event.awaited = false; ts->log_and_monitor->apm_callbacks.server_heartbeat_succeeded (&event); } @@ -1359,7 +1390,7 @@ static void _mongoc_topology_scanner_monitor_heartbeat_failed (const mongoc_topology_scanner_t *ts, const mongoc_host_list_t *host, const bson_error_t *error, - int64_t duration_usec) + mlib_duration elapsed) { mongoc_structured_log (ts->log_and_monitor->structured_log, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, @@ -1369,7 +1400,7 @@ _mongoc_topology_scanner_monitor_heartbeat_failed (const mongoc_topology_scanner utf8 ("serverHost", host->host), int32 ("serverPort", host->port), boolean ("awaited", false), - monotonic_time_duration (duration_usec), + monotonic_time_duration (mlib_microseconds_count (elapsed)), error ("failure", error)); if (ts->log_and_monitor->apm_callbacks.server_heartbeat_failed) { @@ -1377,7 +1408,7 @@ _mongoc_topology_scanner_monitor_heartbeat_failed (const mongoc_topology_scanner event.host = host; event.context = ts->log_and_monitor->apm_context; event.error = error; - event.duration_usec = duration_usec; + event.duration_usec = mlib_microseconds_count (elapsed); event.awaited = false; ts->log_and_monitor->apm_callbacks.server_heartbeat_failed (&event); } @@ -1410,8 +1441,8 @@ _cancel_commands_excluding (mongoc_topology_scanner_node_t *node, mongoc_async_c mongoc_async_cmd_t *iter; DL_FOREACH (node->ts->async->cmds, iter) { - if ((mongoc_topology_scanner_node_t *) iter->data == node && iter != acmd) { - iter->state = MONGOC_ASYNC_CMD_CANCELED_STATE; + if (acmd && _is_sibling_command (iter, acmd)) { + _acmd_cancel (iter); } } } @@ -1423,26 +1454,13 @@ _count_acmds (mongoc_topology_scanner_node_t *node) int count = 0; DL_FOREACH (node->ts->async->cmds, iter) { - if ((mongoc_topology_scanner_node_t *) iter->data == node) { + if (_scanner_node_of (iter) == node) { ++count; } } return count; } -static void -_jumpstart_other_acmds (mongoc_topology_scanner_node_t *node, mongoc_async_cmd_t *acmd) -{ - mongoc_async_cmd_t *iter; - DL_FOREACH (node->ts->async->cmds, iter) - { - if ((mongoc_topology_scanner_node_t *) iter->data == node && iter != acmd && - acmd->initiate_delay_ms < iter->initiate_delay_ms) { - iter->initiate_delay_ms = BSON_MAX (iter->initiate_delay_ms - HAPPY_EYEBALLS_DELAY_MS, 0); - } - } -} - void _mongoc_topology_scanner_set_server_api (mongoc_topology_scanner_t *ts, const mongoc_server_api_t *api) { diff --git a/src/libmongoc/src/mongoc/mongoc-util-private.h b/src/libmongoc/src/mongoc/mongoc-util-private.h index c606e384d5..853f8a7516 100644 --- a/src/libmongoc/src/mongoc/mongoc-util-private.h +++ b/src/libmongoc/src/mongoc/mongoc-util-private.h @@ -62,9 +62,6 @@ _mongoc_rand_simple (unsigned int *seed); char * _mongoc_hex_md5 (const char *input); -void -_mongoc_usleep (int64_t usec); - /* Get the current time as a number of milliseconds since the Unix Epoch. */ int64_t _mongoc_get_real_time_ms (void); diff --git a/src/libmongoc/src/mongoc/mongoc-util.c b/src/libmongoc/src/mongoc/mongoc-util.c index 77619f1dcb..540b80b77a 100644 --- a/src/libmongoc/src/mongoc/mongoc-util.c +++ b/src/libmongoc/src/mongoc/mongoc-util.c @@ -14,6 +14,7 @@ * limitations under the License. */ + #ifdef _WIN32 #define _CRT_RAND_S #endif @@ -33,8 +34,10 @@ #include #include +#include #include #include +#include #include @@ -107,27 +110,7 @@ mongoc_usleep_default_impl (int64_t usec, void *user_data) { BSON_UNUSED (user_data); -#ifdef _WIN32 - LARGE_INTEGER ft; - HANDLE timer; - - BSON_ASSERT (usec >= 0); - - ft.QuadPart = -(10 * usec); - timer = CreateWaitableTimer (NULL, true, NULL); - SetWaitableTimer (timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject (timer, INFINITE); - CloseHandle (timer); -#else - BSON_ASSERT (usec >= 0); - usleep ((useconds_t) usec); -#endif -} - -void -_mongoc_usleep (int64_t usec) -{ - mongoc_usleep_default_impl (usec, NULL); + mlib_sleep_for (usec, us); } diff --git a/src/libmongoc/tests/TestSuite.h b/src/libmongoc/tests/TestSuite.h index 02d37231c6..ed362c268c 100644 --- a/src/libmongoc/tests/TestSuite.h +++ b/src/libmongoc/tests/TestSuite.h @@ -25,6 +25,8 @@ #include +#include + #include #include #include @@ -577,7 +579,7 @@ _test_error (const char *format, ...) BSON_GNUC_PRINTF (1, 2); BSON_FUNC); \ abort (); \ } \ - _mongoc_usleep (10 * 1000); \ + mlib_sleep_for (10, ms); \ } \ } while (0) diff --git a/src/libmongoc/tests/json-test-operations.c b/src/libmongoc/tests/json-test-operations.c index 6532d885c7..843ca54599 100644 --- a/src/libmongoc/tests/json-test-operations.c +++ b/src/libmongoc/tests/json-test-operations.c @@ -32,6 +32,8 @@ #include +#include + #include #include #include @@ -2071,7 +2073,7 @@ wait_for_event (json_test_ctx_t *ctx, const bson_t *operation) test_error ("Unknown event: %s", event_name); } if (!satisfied) { - _mongoc_usleep (WAIT_FOR_EVENT_TICK_MS * 1000); + mlib_sleep_for (WAIT_FOR_EVENT_TICK_MS, ms); } } @@ -2109,7 +2111,7 @@ wait_for_primary_change (json_test_ctx_t *ctx, const bson_t *operation) bson_mutex_unlock (&ctx->mutex); if (!satisfied) { - _mongoc_usleep (10 * 1000); + mlib_sleep_for (10, ms); } } @@ -2419,7 +2421,7 @@ json_test_operation (json_test_ctx_t *ctx, mongoc_client_destroy (client); } else if (!strcmp (op_name, "wait")) { - _mongoc_usleep (bson_lookup_int32 (operation, "arguments.ms") * 1000); + mlib_sleep_for (bson_lookup_int32 (operation, "arguments.ms"), ms); } else if (!strcmp (op_name, "recordPrimary")) { /* It doesn't matter who the primary is. We just want to assert in * tests later that the primary changed x times after this operation. diff --git a/src/libmongoc/tests/mock_server/mock-server.c b/src/libmongoc/tests/mock_server/mock-server.c index 52a16578de..ba015f894a 100644 --- a/src/libmongoc/tests/mock_server/mock-server.c +++ b/src/libmongoc/tests/mock_server/mock-server.c @@ -34,6 +34,7 @@ #include #include +#include #ifdef BSON_HAVE_STRINGS_H #include @@ -553,7 +554,8 @@ auto_hello (request_t *request, void *data) response_json = bson_as_relaxed_extended_json (&response, 0); if (mock_server_get_rand_delay (request->server)) { - _mongoc_usleep ((int64_t) (rand () % 10) * 1000); + const int random_sleep = rand () % 10; + mlib_sleep_for (random_sleep, ms); } reply_to_request (request, MONGOC_REPLY_NONE, 0, 0, 1, response_json); @@ -1629,7 +1631,7 @@ mock_server_destroy (mock_server_t *server) } bson_mutex_unlock (&server->mutex); - _mongoc_usleep (1000); + mlib_sleep_for (1, ms); } bson_mutex_lock (&server->mutex); diff --git a/src/libmongoc/tests/test-happy-eyeballs.c b/src/libmongoc/tests/test-happy-eyeballs.c index dc8260e885..2965126a72 100644 --- a/src/libmongoc/tests/test-happy-eyeballs.c +++ b/src/libmongoc/tests/test-happy-eyeballs.c @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include @@ -139,13 +141,13 @@ _mock_poll (mongoc_stream_poll_t *streams, size_t nstreams, int32_t timeout) /* if there were active poll responses which were all silenced, * sleep for a little while since subsequent calls to poll may not have * any delay. */ - _mongoc_usleep (5 * 1000); + mlib_sleep_for (5, ms); } return nactive; } static mongoc_stream_t * -_mock_initiator (mongoc_async_cmd_t *acmd) +_mock_connect (mongoc_async_cmd_t *acmd) { mongoc_stream_t *stream = _mongoc_topology_scanner_tcp_initiate (acmd); /* override poll */ @@ -305,7 +307,7 @@ _testcase_run (he_testcase_t *testcase) DL_FOREACH (ts->async->cmds, iter) { - iter->initiator = _mock_initiator; + iter->_stream_connect = _mock_connect; } mongoc_topology_scanner_work (ts); @@ -391,7 +393,7 @@ test_happy_eyeballs_dns_cache (void) mongoc_topology_scanner_node_disconnect (testcase.state.ts->nodes, false); /* wait for DNS cache to expire. */ - _mongoc_usleep (2000 * 1000); + mlib_sleep_for (2, s); /* after running once, the topology scanner should have cached the DNS * result for IPv6. It should complete immediately. */ diff --git a/src/libmongoc/tests/test-mongoc-async.c b/src/libmongoc/tests/test-mongoc-async.c index 39ee66562e..4f6c774365 100644 --- a/src/libmongoc/tests/test-mongoc-async.c +++ b/src/libmongoc/tests/test-mongoc-async.c @@ -6,6 +6,8 @@ #include +#include + #include #include #include @@ -47,9 +49,9 @@ static void test_hello_helper (mongoc_async_cmd_t *acmd, mongoc_async_cmd_result_t result, const bson_t *bson, - int64_t duration_usec) + mlib_duration duration_usec) { - struct result *r = (struct result *) acmd->data; + struct result *r = _acmd_userdata (struct result, acmd); bson_iter_t iter; bson_error_t *error = &acmd->error; @@ -78,7 +80,7 @@ test_hello_impl (bool with_ssl) mock_server_t *servers[NSERVERS]; mongoc_async_t *async; mongoc_stream_t *sock_streams[NSERVERS]; - mongoc_async_cmd_setup_t setup = NULL; + mongoc_async_cmd_stream_setup_cb setup = NULL; void *setup_ctx = NULL; uint16_t ports[NSERVERS]; struct result results[NSERVERS]; @@ -139,8 +141,8 @@ test_hello_impl (bool with_ssl) sock_streams[i], false, NULL /* dns result, n/a. */, - NULL, /* initiator. */ - 0, /* initiate delay. */ + NULL, /* initiator. */ + mlib_duration (), /* No initiate delay. */ setup, setup_ctx, "admin", @@ -148,7 +150,7 @@ test_hello_impl (bool with_ssl) MONGOC_OP_CODE_QUERY, /* used by legacy hello */ &test_hello_helper, (void *) &results[i], - TIMEOUT); + mlib_duration (TIMEOUT, ms)); } future = future_async_run (async); @@ -213,9 +215,9 @@ static void test_large_hello_helper (mongoc_async_cmd_t *acmd, mongoc_async_cmd_result_t result, const bson_t *bson, - int64_t duration_usec) + mlib_duration deadline) { - BSON_UNUSED (duration_usec); + BSON_UNUSED (deadline); bson_iter_t iter; bson_error_t *error = &acmd->error; @@ -279,8 +281,8 @@ test_large_hello (void *ctx) sock_stream, false, /* is setup done. */ NULL /* dns result, n/a. */, - NULL, /* initiator. */ - 0, /* initiate delay. */ + NULL, /* initiator. */ + mlib_duration (), /* initiate delay. */ #ifdef MONGOC_ENABLE_SSL test_framework_get_ssl () ? mongoc_async_cmd_tls_setup : NULL, #else @@ -292,7 +294,7 @@ test_large_hello (void *ctx) MONGOC_OP_CODE_QUERY, /* used by legacy hello */ &test_large_hello_helper, NULL, - TIMEOUT); + mlib_duration (TIMEOUT, ms)); mongoc_async_run (async); mongoc_async_destroy (async); @@ -310,19 +312,19 @@ static void test_hello_delay_callback (mongoc_async_cmd_t *acmd, mongoc_async_cmd_result_t result, const bson_t *bson, - int64_t duration_usec) + mlib_duration duration_usec) { BSON_UNUSED (result); BSON_UNUSED (bson); BSON_UNUSED (duration_usec); - ((stream_with_result_t *) acmd->data)->finished = true; + _acmd_userdata (stream_with_result_t, acmd)->finished = true; } static mongoc_stream_t * test_hello_delay_initializer (mongoc_async_cmd_t *acmd) { - return ((stream_with_result_t *) acmd->data)->stream; + return _acmd_userdata (stream_with_result_t, acmd)->stream; } static void @@ -346,15 +348,15 @@ test_hello_delay (void) false, /* is setup done. */ NULL, /* dns result. */ test_hello_delay_initializer, - 100, /* delay 100ms. */ - NULL, /* setup function. */ - NULL, /* setup ctx. */ + mlib_duration (100, ms), /* delay 100ms. */ + NULL, /* setup function. */ + NULL, /* setup ctx. */ "admin", &hello_cmd, MONGOC_OP_CODE_QUERY, /* used by legacy hello */ &test_hello_delay_callback, &stream_with_result, - TIMEOUT); + mlib_duration (TIMEOUT, ms)); mongoc_async_run (async); diff --git a/src/libmongoc/tests/test-mongoc-background-monitoring.c b/src/libmongoc/tests/test-mongoc-background-monitoring.c index 241155a2bd..6bfedbff0c 100644 --- a/src/libmongoc/tests/test-mongoc-background-monitoring.c +++ b/src/libmongoc/tests/test-mongoc-background-monitoring.c @@ -25,6 +25,8 @@ #include +#include + #include #include #include @@ -294,7 +296,7 @@ tf_destroy (test_fixture_t *tf) * Used to make observations that a scan doesn't occur when a test fixture is * configured with a faster heartbeat. */ -#define WAIT_TWO_MIN_HEARTBEAT_MS _mongoc_usleep (2 * FAST_HEARTBEAT_MS * 1000) +#define WAIT_TWO_MIN_HEARTBEAT_MS() mlib_sleep_for ((FAST_HEARTBEAT_MS, ms), mul, 2) static void _signal_shutdown (test_fixture_t *tf) @@ -404,7 +406,7 @@ test_connect_hangup (void) OBSERVE (tf, !tf->observations->awaited); /* No retry occurs since the server was never discovered. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 1); tf_destroy (tf); } @@ -429,7 +431,7 @@ test_connect_badreply (void) OBSERVE_SOON (tf, tf->observations->sd_type == MONGOC_SERVER_UNKNOWN); /* No retry occurs since the server was never discovered. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 1); tf_destroy (tf); } @@ -475,7 +477,7 @@ test_connect_requestscan (void) /* Because the request occurred during the scan, no subsequent scan occurs. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 1); OBSERVE (tf, tf->observations->n_heartbeat_succeeded == 1); OBSERVE (tf, tf->observations->n_heartbeat_failed == 0); @@ -653,7 +655,7 @@ test_retry_shutdown (void) reply_to_request_with_ok_and_destroy (request); /* No retry occurs. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 2); OBSERVE (tf, tf->observations->n_heartbeat_succeeded == 2); OBSERVE (tf, tf->observations->n_heartbeat_failed == 0); @@ -703,7 +705,7 @@ test_repeated_requestscan (void) for (i = 0; i < 10; i++) { _request_scan (tf); } - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 1); reply_to_request_with_ok_and_destroy (request); OBSERVE_SOON (tf, tf->observations->n_heartbeat_succeeded == 1); @@ -724,7 +726,7 @@ test_sleep_after_scan (void) OBSERVE (tf, tf->observations->n_heartbeat_started == 1); reply_to_request_with_ok_and_destroy (request); OBSERVE_SOON (tf, tf->observations->n_heartbeat_succeeded == 1); - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); /* No subsequent command send. */ OBSERVE (tf, tf->observations->n_heartbeat_started == 1); tf_destroy (tf); @@ -822,7 +824,7 @@ test_streaming_shutdown (void) OBSERVE (tf, tf->observations->n_heartbeat_started == 2); _signal_shutdown (tf); /* This should cancel the hello immediately. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); /* No further hello commands should be sent. */ OBSERVE (tf, tf->observations->n_heartbeat_started == 2); request_destroy (request); @@ -849,7 +851,7 @@ test_streaming_cancel (void) OBSERVE_SOON (tf, tf->observations->n_server_changed == 1); /* The cancellation closes the connection and waits before creating a new * connection. Check that no new heartbeat was started. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 2); _request_scan (tf); /* The handshake will be handled by the auto responder. */ @@ -999,7 +1001,7 @@ test_moretocome_shutdown (void) * processing the last reply. Requesting shutdown cancels. */ _signal_shutdown (tf); - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); /* No further heartbeats are attempted. */ OBSERVE (tf, tf->observations->n_heartbeat_succeeded == 2); request_destroy (request); @@ -1035,7 +1037,7 @@ test_moretocome_cancel (void) /* The cancellation closes the connection and waits before creating a new * connection. Check that no new heartbeat was started. */ - WAIT_TWO_MIN_HEARTBEAT_MS; + WAIT_TWO_MIN_HEARTBEAT_MS (); OBSERVE (tf, tf->observations->n_heartbeat_started == 3); _request_scan (tf); /* The handshake will be handled by the auto responder. */ diff --git a/src/libmongoc/tests/test-mongoc-client-pool.c b/src/libmongoc/tests/test-mongoc-client-pool.c index f1004b1756..d713d1973a 100644 --- a/src/libmongoc/tests/test-mongoc-client-pool.c +++ b/src/libmongoc/tests/test-mongoc-client-pool.c @@ -5,6 +5,8 @@ #include +#include + #include #include @@ -304,7 +306,7 @@ static BSON_THREAD_FUN (worker, arg) pool_timeout_args_t *args = arg; mongoc_client_t *client = mongoc_client_pool_pop (args->pool); BSON_ASSERT (client); - _mongoc_usleep (10); + mlib_sleep_for (10, us); mongoc_client_pool_push (args->pool, client); bson_mutex_lock (&args->mutex); /* notify main thread that current thread has terminated */ diff --git a/src/libmongoc/tests/test-mongoc-client-session.c b/src/libmongoc/tests/test-mongoc-client-session.c index 373e0a8b59..87d6ca22d7 100644 --- a/src/libmongoc/tests/test-mongoc-client-session.c +++ b/src/libmongoc/tests/test-mongoc-client-session.c @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -229,7 +230,7 @@ _test_session_pool_timeout (bool pooled) mongoc_client_session_destroy (s); BSON_ASSERT (!mongoc_server_session_pool_is_empty (client->topology->session_pool)); - _mongoc_usleep (1500 * 1000); + mlib_sleep_for (1500, ms); /* getting a new client session must start a new server session */ s = mongoc_client_start_session (client, NULL, &error); @@ -313,7 +314,7 @@ _test_session_pool_reap (bool pooled) mongoc_client_session_destroy (a); BSON_ASSERT (!mongoc_server_session_pool_is_empty (client->topology->session_pool)); /* session is pooled */ - _mongoc_usleep (1500 * 1000); + mlib_sleep_for (1500, ms); /* * returning session B causes session A to be reaped diff --git a/src/libmongoc/tests/test-mongoc-client-side-encryption.c b/src/libmongoc/tests/test-mongoc-client-side-encryption.c index 91225dc587..54443ddb1d 100644 --- a/src/libmongoc/tests/test-mongoc-client-side-encryption.c +++ b/src/libmongoc/tests/test-mongoc-client-side-encryption.c @@ -17,6 +17,10 @@ #include #include +#include + +#include + #include #include @@ -6053,7 +6057,7 @@ static BSON_THREAD_FUN (listen_socket, arg) // listen on socket r = mongoc_socket_listen (socket, 100); BSON_ASSERT (r == 0); - _mongoc_usleep (1000); // wait to see if received connection + mlib_sleep_for (1, ms); mongoc_socket_t *ret = mongoc_socket_accept (socket, bson_get_monotonic_time () + 100); if (ret) { // not null received a connection and test should fail diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 45722dd1d3..d8cc10fa93 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -9,6 +9,8 @@ #include +#include + #include #ifdef MONGOC_ENABLE_SSL #include @@ -798,7 +800,7 @@ test_wire_version (void) WIRE_VERSION_MIN - 1); /* wait until it's time for next heartbeat */ - _mongoc_usleep (600 * 1000); + mlib_sleep_for (600, ms); sd = mongoc_client_select_server (client, true, NULL, &error); BSON_ASSERT (!sd); BSON_ASSERT (error.domain == MONGOC_ERROR_PROTOCOL); @@ -814,7 +816,7 @@ test_wire_version (void) WIRE_VERSION_MAX); /* wait until it's time for next heartbeat */ - _mongoc_usleep (600 * 1000); + mlib_sleep_for (600, ms); sd = mongoc_client_select_server (client, true, NULL, &error); ASSERT_OR_PRINT (sd, error); mongoc_server_description_destroy (sd); @@ -2214,7 +2216,7 @@ test_mongoc_client_descriptions_pooled (void *unused) /* wait for background thread to discover all members */ start = bson_get_monotonic_time (); do { - _mongoc_usleep (1000); + mlib_sleep_for (1, ms); /* Windows IPv4 tasks may take longer to connect since connection to the * first address returned by getaddrinfo may be IPv6, and failure to * connect may take a couple seconds. See CDRIVER-3639. */ @@ -2468,7 +2470,7 @@ _test_mongoc_client_select_server_retry (bool retry_succeeds) mongoc_server_description_destroy (sd); /* let socketCheckIntervalMS pass */ - _mongoc_usleep (100 * 1000); + mlib_sleep_for (100, ms); /* second selection requires ping, which fails */ future = future_client_select_server (client, true, NULL, &error); @@ -2551,7 +2553,7 @@ _test_mongoc_client_fetch_stream_retry (bool retry_succeeds) future_destroy (future); /* let socketCheckIntervalMS pass */ - _mongoc_usleep (100 * 1000); + mlib_sleep_for (100, ms); /* second selection requires ping, which fails */ future = future_client_command_simple (client, "db", tmp_bson ("{'cmd': 1}"), NULL, NULL, &error); @@ -2828,7 +2830,7 @@ _force_hello_with_ping (mongoc_client_t *client, int heartbeat_ms) BSON_ASSERT_PARAM (client); /* Wait until we're overdue to send a hello */ - _mongoc_usleep (heartbeat_ms * 2 * 1000); + mlib_sleep_for ((heartbeat_ms, ms), mul, 2); /* Send a ping */ future = future_client_command_simple (client, "admin", tmp_bson ("{'ping': 1}"), NULL, NULL, NULL); @@ -2986,7 +2988,10 @@ _test_client_sends_handshake (bool pooled) /* We're in cooldown for the next few seconds, so we're not * allowed to send hellos. Wait for the cooldown to end. */ - _mongoc_usleep ((MONGOC_TOPOLOGY_COOLDOWN_MS + 1000) * 1000); + mlib_sleep_for ( // + (MONGOC_TOPOLOGY_COOLDOWN_MS, ms), + plus, + (1, s)); future = _force_hello_with_ping (client, heartbeat_ms); } @@ -3843,7 +3848,7 @@ void test_client_install (TestSuite *suite) { TestSuite_AddLive (suite, "/Client/ipv6/single", test_mongoc_client_ipv6_single); - TestSuite_AddLive (suite, "/Client/ipv6/single", test_mongoc_client_ipv6_pooled); + TestSuite_AddLive (suite, "/Client/ipv6/pooled", test_mongoc_client_ipv6_pooled); TestSuite_AddFull ( suite, "/Client/authenticate", test_mongoc_client_authenticate, NULL, NULL, test_framework_skip_if_no_auth); diff --git a/src/libmongoc/tests/test-mongoc-cluster.c b/src/libmongoc/tests/test-mongoc-cluster.c index 255397dd64..a9d0d4d0d4 100644 --- a/src/libmongoc/tests/test-mongoc-cluster.c +++ b/src/libmongoc/tests/test-mongoc-cluster.c @@ -7,6 +7,8 @@ #include +#include + #include #include #include @@ -500,7 +502,7 @@ _test_cluster_time (bool pooled, command_fn_t command) client = mongoc_client_pool_pop (pool); /* CDRIVER-3596 - prevent client discovery of the pool interfering with * the test operations. */ - _mongoc_usleep (5000 * 1000); /* 5 s */ + mlib_sleep_for (5, s); } else { client = test_framework_new_default_client (); mongoc_client_set_apm_callbacks (client, callbacks, &cluster_time_test); @@ -862,7 +864,7 @@ _test_cluster_time_comparison (bool pooled) mongoc_client_pool_destroy (pool); } else { /* trigger next heartbeat, it should contain newest cluster time */ - _mongoc_usleep (750 * 1000); /* 750 ms */ + mlib_sleep_for (750, ms); future = future_ping (client, &error); request = mock_server_receives_any_hello_with_match (server, "{'$clusterTime': " diff --git a/src/libmongoc/tests/test-mongoc-counters.c b/src/libmongoc/tests/test-mongoc-counters.c index aefe1a5439..fdff5e1bdd 100644 --- a/src/libmongoc/tests/test-mongoc-counters.c +++ b/src/libmongoc/tests/test-mongoc-counters.c @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -456,7 +457,7 @@ test_counters_streams_timeout (void) reset_all_counters (); future = future_client_command_simple (client, "test", tmp_bson ("{'ping': 1}"), NULL, NULL, &err); request = mock_server_receives_msg (server, MONGOC_QUERY_NONE, tmp_bson ("{'ping': 1}")); - _mongoc_usleep (350); + mlib_sleep_for (350, us); request_destroy (request); ret = future_get_bool (future); BSON_ASSERT (!ret); @@ -1221,7 +1222,7 @@ wait_for_background_threads (rpc_op_egress_counters expected) return; } - _mongoc_usleep (100000); // 100 ms. + mlib_sleep_for (100, ms); current = rpc_op_egress_counters_current (); } diff --git a/src/libmongoc/tests/test-mongoc-dns.c b/src/libmongoc/tests/test-mongoc-dns.c index 20b1f3f433..3fafb8d837 100644 --- a/src/libmongoc/tests/test-mongoc-dns.c +++ b/src/libmongoc/tests/test-mongoc-dns.c @@ -7,6 +7,8 @@ #include #include +#include + #ifdef MONGOC_ENABLE_SSL #include @@ -791,7 +793,7 @@ _prose_test_update_srv_single (void *resource) client = resource; - _mongoc_usleep (2000 * RESCAN_INTERVAL_MS); + mlib_sleep_for ((RESCAN_INTERVAL_MS, ms), mul, 2); /* Avoid ping given `loadBalanced=true`; see prose test 9. */ if (!mongoc_uri_get_option_as_bool (client->uri, MONGOC_URI_LOADBALANCED, false)) { @@ -804,7 +806,7 @@ _prose_test_update_srv_pooled (void *resource) { BSON_ASSERT_PARAM (resource); - _mongoc_usleep (2000 * RESCAN_INTERVAL_MS); + mlib_sleep_for ((RESCAN_INTERVAL_MS, ms), mul, 2); } typedef struct { diff --git a/src/libmongoc/tests/test-mongoc-interrupt.c b/src/libmongoc/tests/test-mongoc-interrupt.c index 12f82754b6..a06b66f7ff 100644 --- a/src/libmongoc/tests/test-mongoc-interrupt.c +++ b/src/libmongoc/tests/test-mongoc-interrupt.c @@ -20,6 +20,8 @@ #include +#include + #include #include #include @@ -39,7 +41,7 @@ BSON_THREAD_FUN (_interrupt, future_void) future = future_void; interrupt = future_get_param (future, 0)->value.void_ptr_value; - _mongoc_usleep (10 * 1000); + mlib_sleep_for (10, ms); _mongoc_interrupt_interrupt (interrupt); return_value.type = future_value_void_type; future_resolve (future, return_value); diff --git a/src/libmongoc/tests/test-mongoc-max-staleness.c b/src/libmongoc/tests/test-mongoc-max-staleness.c index ffca735f71..5ef140e817 100644 --- a/src/libmongoc/tests/test-mongoc-max-staleness.c +++ b/src/libmongoc/tests/test-mongoc-max-staleness.c @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -223,15 +225,15 @@ _test_last_write_date (bool pooled) r = mongoc_collection_insert_one (collection, tmp_bson ("{}"), NULL, NULL, &error); ASSERT_OR_PRINT (r, error); - _mongoc_usleep (1000 * 1000); + mlib_sleep_for (1, s); s0 = mongoc_topology_select (client->topology, MONGOC_SS_WRITE, TEST_SS_LOG_CONTEXT, NULL, NULL, &error); ASSERT_OR_PRINT (s0, error); - _mongoc_usleep (1000 * 1000); + mlib_sleep_for (1, s); r = mongoc_collection_insert_one (collection, tmp_bson ("{}"), NULL, NULL, &error); ASSERT_OR_PRINT (r, error); - _mongoc_usleep (1000 * 1000); + mlib_sleep_for (1, s); s1 = mongoc_topology_select (client->topology, MONGOC_SS_WRITE, TEST_SS_LOG_CONTEXT, NULL, NULL, &error); ASSERT_OR_PRINT (s1, error); ASSERT_CMPINT64 (s1->last_write_date_ms, !=, (int64_t) -1); diff --git a/src/libmongoc/tests/test-mongoc-primary-stepdown.c b/src/libmongoc/tests/test-mongoc-primary-stepdown.c index bfabdf2873..8d8534e248 100644 --- a/src/libmongoc/tests/test-mongoc-primary-stepdown.c +++ b/src/libmongoc/tests/test-mongoc-primary-stepdown.c @@ -9,6 +9,8 @@ #include +#include + #include #include #include @@ -124,7 +126,7 @@ _run_test_single_or_pooled (_test_fn_t test, bool use_pooled) _setup_test_with_client (client); /* Wait one second to be assured that the RTT connection has been * established as well. */ - _mongoc_usleep (1000 * 1000); + mlib_sleep_for (1, s); test (client); mongoc_client_pool_push (pool, client); mongoc_client_pool_destroy (pool); diff --git a/src/libmongoc/tests/test-mongoc-sample-commands.c b/src/libmongoc/tests/test-mongoc-sample-commands.c index a0d7f05cde..b56063c114 100644 --- a/src/libmongoc/tests/test-mongoc-sample-commands.c +++ b/src/libmongoc/tests/test-mongoc-sample-commands.c @@ -23,17 +23,21 @@ * These are the C examples for that page. */ -/* clang-format off */ -#include -#include -#include -#include +#include "./TestSuite.h" +#include "./test-conveniences.h" +#include "./test-libmongoc.h" + #include +#include +#include -#include -#include -#include +#include + +#include + +#include +/* clang-format off */ typedef void (*sample_command_fn_t) (mongoc_database_t *db); typedef void (*sample_txn_command_fn_t) (mongoc_client_t *client); @@ -816,7 +820,7 @@ test_example_20 (mongoc_database_t *db) } /* End Example 20 */ -done: +done: /* Start Example 20 Post */ bson_destroy (&reply); mongoc_bulk_operation_destroy (bulk); @@ -2889,7 +2893,7 @@ BSON_THREAD_FUN (insert_docs, p) } bson_mutex_unlock (&ctx->lock); - _mongoc_usleep (100 * 1000); /* 100 ms */ + mlib_sleep_for (100, ms); } } @@ -3050,7 +3054,7 @@ test_sample_causal_consistency (mongoc_client_t *client) uint32_t increment; bson_error_t error; bool res; - + ASSERT (client); if (!test_framework_skip_if_no_txns ()) { @@ -3441,12 +3445,12 @@ test_sample_projection_with_aggregation_expressions (mongoc_database_t *db) opts = BCON_NEW ("projection", "{", "_id", BCON_INT32(0), "item", BCON_INT32(1), - "status", "{", - "$switch", "{", - "branches", "[", + "status", "{", + "$switch", "{", + "branches", "[", "{", "case", "{", - "$eq", "[", + "$eq", "[", "$status", BCON_UTF8("A"), "]", "}", @@ -3454,7 +3458,7 @@ test_sample_projection_with_aggregation_expressions (mongoc_database_t *db) "}", "{", "case", "{", - "$eq", "[", + "$eq", "[", "$status", BCON_UTF8("D"), "]", "}", @@ -3464,11 +3468,11 @@ test_sample_projection_with_aggregation_expressions (mongoc_database_t *db) "default", BCON_UTF8("No status found"), "}", "}", - "area", "{", - "$concat", "[", - "{", - "$toString", "{", - "$multiply", "[", + "area", "{", + "$concat", "[", + "{", + "$toString", "{", + "$multiply", "[", BCON_UTF8("$size.h"), BCON_UTF8("$size.w"), "]", @@ -3478,7 +3482,7 @@ test_sample_projection_with_aggregation_expressions (mongoc_database_t *db) BCON_UTF8("$size.uom"), "]", "}", - "reportNumber", "{", + "reportNumber", "{", "$literal", BCON_INT32(1), "}", "}"); @@ -3635,7 +3639,7 @@ insert_employee (mongoc_client_t *client, int employee) mongoc_collection_t *events; bson_error_t error; bool r; - + ASSERT (client); employees = mongoc_client_get_collection (client, "hr", "employees"); diff --git a/src/libmongoc/tests/test-mongoc-sdam-monitoring.c b/src/libmongoc/tests/test-mongoc-sdam-monitoring.c index 0365b48e33..0fcb15654d 100644 --- a/src/libmongoc/tests/test-mongoc-sdam-monitoring.c +++ b/src/libmongoc/tests/test-mongoc-sdam-monitoring.c @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -1059,7 +1061,7 @@ smm_new (const char *mode) static bool smm_wait (smm_t *t, size_t count) { - int64_t started = bson_get_monotonic_time (); + const mlib_timer deadline = mlib_expires_after (10, s); while (true) { bson_mutex_lock (&t->lock); if (t->events_len >= count) { @@ -1068,12 +1070,11 @@ smm_wait (smm_t *t, size_t count) } bson_mutex_unlock (&t->lock); - int64_t now = bson_get_monotonic_time (); // Timeout - if (now - started > 10 * 1000 * 1000) { + if (mlib_timer_is_expired (deadline)) { break; } - _mongoc_usleep (500 * 1000); // Sleep for 500ms. + mlib_sleep_for (500, ms); } return false; } diff --git a/src/libmongoc/tests/test-mongoc-sdam.c b/src/libmongoc/tests/test-mongoc-sdam.c index d6924944ab..ce91a41e20 100644 --- a/src/libmongoc/tests/test-mongoc-sdam.c +++ b/src/libmongoc/tests/test-mongoc-sdam.c @@ -7,6 +7,9 @@ #include +#include +#include + #include #include @@ -751,9 +754,9 @@ heartbeat_succeeded (const mongoc_apm_server_heartbeat_succeeded_t *event) #endif } -#define RTT_TEST_TIMEOUT_SEC 60 -#define RTT_TEST_INITIAL_SLEEP_SEC 2 -#define RTT_TEST_TICK_MS 10 +#define RTT_TEST_TIMEOUT mlib_duration (1, min) +#define RTT_TEST_INITIAL_SLEEP mlib_duration (2, s) +#define RTT_TEST_TICK mlib_duration (10, ms) static void test_prose_rtt (void *unused) @@ -771,7 +774,6 @@ test_prose_rtt (void *unused) prose_test_ctx_t ctx; bson_t cmd; bool ret; - int64_t start_us; bool satisfied; int64_t rtt = 0; @@ -796,7 +798,7 @@ test_prose_rtt (void *unused) /* Sleep for RTT_TEST_INITIAL_SLEEP_SEC seconds to allow multiple heartbeats * to succeed. */ - _mongoc_usleep (RTT_TEST_INITIAL_SLEEP_SEC * 1000 * 1000); + mlib_sleep_for (RTT_TEST_INITIAL_SLEEP); /* Set a failpoint to make hello commands take longer. */ bson_init (&cmd); @@ -823,8 +825,8 @@ test_prose_rtt (void *unused) /* Wait for the server's RTT to exceed 250ms. If this does not happen for * RTT_TEST_TIMEOUT_SEC seconds, consider it a failure. */ satisfied = false; - start_us = bson_get_monotonic_time (); - while (!satisfied && bson_get_monotonic_time () < start_us + RTT_TEST_TIMEOUT_SEC * 1000 * 1000) { + mlib_timer deadline = mlib_expires_after (RTT_TEST_TIMEOUT); + while (!satisfied && !mlib_timer_is_expired (deadline)) { mongoc_server_description_t *sd; sd = mongoc_client_select_server (client, true, NULL /* read prefs */, &error); @@ -834,11 +836,13 @@ test_prose_rtt (void *unused) satisfied = true; } mongoc_server_description_destroy (sd); - _mongoc_usleep (RTT_TEST_TICK_MS * 1000); + mlib_sleep_for (RTT_TEST_TICK); } if (!satisfied) { - test_error ("After %d seconds, the latest observed RTT was only %" PRId64, RTT_TEST_TIMEOUT_SEC, rtt); + test_error ("After %d seconds, the latest observed RTT was only %" PRId64, + (int) mlib_seconds_count (RTT_TEST_TIMEOUT), + rtt); } /* Disable the failpoint. */ diff --git a/src/libmongoc/tests/test-mongoc-socket.c b/src/libmongoc/tests/test-mongoc-socket.c index d9626a4eb2..a65e2ae7d5 100644 --- a/src/libmongoc/tests/test-mongoc-socket.c +++ b/src/libmongoc/tests/test-mongoc-socket.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -77,7 +78,7 @@ static BSON_THREAD_FUN (socket_test_server, data_) strcpy (buf, "pong"); - _mongoc_usleep (data->server_sleep_ms * 1000); + mlib_sleep_for (data->server_sleep_ms, ms); r = mongoc_stream_writev (stream, &iov, 1, TIMEOUT); /* if we sleep the client times out, else assert the client reads the data */ @@ -157,7 +158,7 @@ static BSON_THREAD_FUN (socket_test_client, data_) start = bson_get_monotonic_time (); while (!mongoc_stream_check_closed (stream)) { ASSERT_CMPINT64 (bson_get_monotonic_time (), <, start + 1000 * 1000); - _mongoc_usleep (1000); + mlib_sleep_for (1, ms); } BSON_ASSERT (!mongoc_stream_timed_out (stream)); } else { diff --git a/src/libmongoc/tests/test-mongoc-speculative-auth.c b/src/libmongoc/tests/test-mongoc-speculative-auth.c index 1a83c82a79..5079b55781 100644 --- a/src/libmongoc/tests/test-mongoc-speculative-auth.c +++ b/src/libmongoc/tests/test-mongoc-speculative-auth.c @@ -15,6 +15,8 @@ */ #include + +#include #ifdef _POSIX_VERSION #include #endif @@ -101,7 +103,8 @@ _auto_hello_without_speculative_auth (request_t *request, void *data) quotes_replaced = single_quotes_to_double (response_json); if (mock_server_get_rand_delay (request->server)) { - _mongoc_usleep ((int64_t) (rand () % 10) * 1000); + int rand_ms = rand () % 10; + mlib_sleep_for (rand_ms, ms); } reply_to_request (request, MONGOC_REPLY_NONE, 0, 0, 1, response_json); diff --git a/src/libmongoc/tests/test-mongoc-stream-tls-error.c b/src/libmongoc/tests/test-mongoc-stream-tls-error.c index c4c68a0f7a..d4f57d2345 100644 --- a/src/libmongoc/tests/test-mongoc-stream-tls-error.c +++ b/src/libmongoc/tests/test-mongoc-stream-tls-error.c @@ -4,6 +4,8 @@ #include #include +#include + #ifdef MONGOC_ENABLE_SSL_OPENSSL #include #endif @@ -77,7 +79,7 @@ static BSON_THREAD_FUN (ssl_error_server, ptr) switch (data->behavior) { case SSL_TEST_BEHAVIOR_STALL_BEFORE_HANDSHAKE: - _mongoc_usleep (data->handshake_stall_ms * 1000); + mlib_sleep_for (data->handshake_stall_ms, ms); break; case SSL_TEST_BEHAVIOR_HANGUP_AFTER_HANDSHAKE: r = mongoc_stream_tls_handshake_block (ssl_stream, data->host, TIMEOUT, &error); diff --git a/src/libmongoc/tests/test-mongoc-topology-reconcile.c b/src/libmongoc/tests/test-mongoc-topology-reconcile.c index 25e9d6edc1..72a7cf51da 100644 --- a/src/libmongoc/tests/test-mongoc-topology-reconcile.c +++ b/src/libmongoc/tests/test-mongoc-topology-reconcile.c @@ -6,6 +6,8 @@ #include #include +#include + #include #include #include @@ -248,7 +250,7 @@ _test_topology_reconcile_sharded (bool pooled) request_destroy (request); /* make sure the mongos response is processed first */ - _mongoc_usleep (1000 * 1000); + mlib_sleep_for (1, s); /* replica set secondary - topology removes it */ request = mock_server_receives_any_hello (secondary); diff --git a/src/libmongoc/tests/test-mongoc-topology-scanner.c b/src/libmongoc/tests/test-mongoc-topology-scanner.c index 809a055193..143aecb5d5 100644 --- a/src/libmongoc/tests/test-mongoc-topology-scanner.c +++ b/src/libmongoc/tests/test-mongoc-topology-scanner.c @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include @@ -185,7 +187,7 @@ test_topology_scanner_discovery (void) request_destroy (request); /* let client process that response */ - _mongoc_usleep (250 * 1000); + mlib_sleep_for (250, ms); /* a check of the secondary is scheduled in this scan */ request = mock_server_receives_any_hello (secondary); @@ -259,13 +261,13 @@ test_topology_scanner_oscillate (void) request_destroy (request); /* let client process that response */ - _mongoc_usleep (250 * 1000); + mlib_sleep_for (250, ms); request = mock_server_receives_any_hello (server1); reply_to_request_simple (request, server1_response); /* we don't schedule another check of server0 */ - _mongoc_usleep (250 * 1000); + mlib_sleep_for (250, ms); BSON_ASSERT (!future_get_mongoc_server_description_ptr (future)); BSON_ASSERT (scanner->async->ncmds == 0); @@ -348,7 +350,7 @@ slow_initiator (const mongoc_uri_t *uri, const mongoc_host_list_t *host, void *u data = (initiator_data_t *) user_data; if (host->port == data->slow_port) { - _mongoc_usleep (500 * 1000); /* 500 ms is longer than connectTimeoutMS */ + mlib_sleep_for (500, ms); /* 500 ms is longer than connectTimeoutMS */ } return mongoc_client_default_stream_initiator (uri, host, data->client, err); @@ -557,7 +559,7 @@ _retired_fails_to_initiate_cb ( } static mongoc_stream_t * -null_initiator (mongoc_async_cmd_t *acmd) +null_connect (mongoc_async_cmd_t *acmd) { BSON_UNUSED (acmd); @@ -598,7 +600,7 @@ test_topology_retired_fails_to_initiate (void) * a failed mongoc_socket_new or mongoc_stream_connect. */ DL_FOREACH (scanner->async->cmds, acmd) { - scanner->async->cmds->initiator = null_initiator; + scanner->async->cmds->_stream_connect = null_connect; } mongoc_topology_scanner_work (scanner); @@ -659,7 +661,7 @@ _test_topology_scanner_does_not_renegotiate (bool pooled) r = mongoc_client_command_simple (client, "admin", tmp_bson ("{'ping': 1}"), NULL, NULL, &error); ASSERT_OR_PRINT (r, error); - _mongoc_usleep (1500 * 1000); /* 1.5 seconds */ + mlib_sleep_for (1500, ms); r = mongoc_client_command_simple (client, "admin", tmp_bson ("{'ping': 1}"), NULL, NULL, &error); ASSERT_OR_PRINT (r, error); diff --git a/src/libmongoc/tests/test-mongoc-topology.c b/src/libmongoc/tests/test-mongoc-topology.c index 36adf70e9b..9e9573ef6d 100644 --- a/src/libmongoc/tests/test-mongoc-topology.c +++ b/src/libmongoc/tests/test-mongoc-topology.c @@ -8,6 +8,8 @@ #include +#include + #include #include #include @@ -389,7 +391,8 @@ _test_server_selection (bool try_once) BSON_ASSERT (client->topology->stale); future_destroy (future); - _mongoc_usleep (510 * 1000); /* one heartbeat, plus a few milliseconds */ + /* one heartbeat, plus a few milliseconds */ + mlib_sleep_for (510, ms); /* second selection, now we try hello again */ future = future_topology_select (client->topology, MONGOC_SS_READ, TEST_SS_LOG_CONTEXT, primary_pref, NULL, &error); @@ -713,7 +716,7 @@ test_cooldown_standalone (void) MONGOC_ERROR_SERVER_SELECTION_FAILURE, "No servers yet eligible for rescan"); - _mongoc_usleep (1000 * 1000); /* 1 second */ + mlib_sleep_for (1, s); /* third selection doesn't try to call hello: we're still in cooldown */ future = future_topology_select (client->topology, MONGOC_SS_READ, TEST_SS_LOG_CONTEXT, primary_pref, NULL, &error); @@ -726,7 +729,8 @@ test_cooldown_standalone (void) future_destroy (future); mock_server_set_request_timeout_msec (server, get_future_timeout_ms ()); - _mongoc_usleep (5100 * 1000); /* 5.1 seconds */ + // 5.1 seconds + mlib_sleep_for (5100, ms); /* cooldown ends, now we try hello again, this time succeeding */ future = future_topology_select (client->topology, MONGOC_SS_READ, TEST_SS_LOG_CONTEXT, primary_pref, NULL, &error); @@ -830,7 +834,7 @@ test_cooldown_rs (void) BSON_ASSERT (!future_get_mongoc_server_description_ptr (future)); future_destroy (future); - _mongoc_usleep (1000 * 1000); /* 1 second */ + mlib_sleep_for (1, s); /* second selection doesn't try hello on server 1: it's in cooldown */ future = future_topology_select (client->topology, MONGOC_SS_READ, TEST_SS_LOG_CONTEXT, primary_pref, NULL, &error); @@ -848,7 +852,8 @@ test_cooldown_rs (void) BSON_ASSERT (!future_get_mongoc_server_description_ptr (future)); future_destroy (future); - _mongoc_usleep (5100 * 1000); /* 5.1 seconds. longer than 5 sec cooldown. */ + // 5.1 seconds, longer than the 5sec cooldown + mlib_sleep_for (5100, ms); /* cooldown ends, now we try hello on server 1, this time succeeding */ future = future_topology_select (client->topology, MONGOC_SS_READ, TEST_SS_LOG_CONTEXT, primary_pref, NULL, &error); @@ -1230,7 +1235,7 @@ test_rtt (void *ctx) future = future_client_command_simple (client, "db", tmp_bson ("{'ping': 1}"), NULL, NULL, &error); request = mock_server_receives_any_hello (server); - _mongoc_usleep (1000 * 1000); /* one second */ + mlib_sleep_for (1, s); reply_to_request ( request, MONGOC_REPLY_NONE, @@ -1410,7 +1415,7 @@ _test_hello_retry_single (bool hangup, size_t n_failures) receives_command (server, future); /* wait for the next server check */ - _mongoc_usleep (600 * 1000); + mlib_sleep_for (600, ms); /* start a {foo: 1} command, server check fails and retries immediately */ future = future_command (client, &error); @@ -1661,7 +1666,7 @@ test_incompatible_error (void) WIRE_VERSION_MAX + 1); /* wait until it's time for next heartbeat */ - _mongoc_usleep (600 * 1000); + mlib_sleep_for (600, ms); ASSERT (!mongoc_client_command_simple ( client, "admin", tmp_bson ("{'" HANDSHAKE_CMD_LEGACY_HELLO "': 1}"), NULL, NULL, &error)); @@ -1940,7 +1945,7 @@ _test_request_scan_on_error ( /* a scan is requested immediately. wait for the scan to finish. */ WAIT_UNTIL (checks_cmp (&checks, "n_started", '=', 4)); } else { - _mongoc_usleep (minHBMS * 2); + mlib_sleep_for ((minHBMS, us), mul, 2); BSON_ASSERT (checks_cmp (&checks, "n_started", '=', 2)); } } else { @@ -2341,7 +2346,7 @@ _test_hello_ok (bool pooled) /* Send off another ping for non-pooled clients, making sure to wait long * enough to require another heartbeat. */ - _mongoc_usleep (600 * 1000); + mlib_sleep_for (600, ms); future = future_client_command_simple (client, "admin", tmp_bson ("{'ping': 1}"), NULL, NULL, &error); } @@ -2369,7 +2374,7 @@ _test_hello_ok (bool pooled) /* Send off another ping for non-pooled clients, making sure to wait long * enough to require another heartbeat. */ - _mongoc_usleep (600 * 1000); + mlib_sleep_for (600, ms); future = future_client_command_simple (client, "admin", tmp_bson ("{'ping': 1}"), NULL, NULL, &error); } @@ -2483,7 +2488,7 @@ test_failure_to_setup_after_retry (void) } // Wait until ready for next topology scan. - _mongoc_usleep (overridden_heartbeat_ms * 1000); + mlib_sleep_for (overridden_heartbeat_ms, ms); // Send another command. future = future_client_command_simple (client, "test", tmp_bson ("{'ping': 1}"), NULL, NULL, &error); diff --git a/src/libmongoc/tests/test-mongoc-usleep.c b/src/libmongoc/tests/test-mongoc-usleep.c index 8b9d8d1a47..df741b06c9 100644 --- a/src/libmongoc/tests/test-mongoc-usleep.c +++ b/src/libmongoc/tests/test-mongoc-usleep.c @@ -10,19 +10,6 @@ #include -static void -test_mongoc_usleep_basic (void) -{ - int64_t start; - int64_t duration; - - start = bson_get_monotonic_time (); - _mongoc_usleep (50 * 1000); /* 50 ms */ - duration = bson_get_monotonic_time () - start; - ASSERT_CMPINT ((int) duration, >, 0); - ASSERT_CMPTIME ((int) duration, 200 * 1000); -} - static void custom_usleep_impl (int64_t usec, void *user_data) { @@ -31,7 +18,6 @@ custom_usleep_impl (int64_t usec, void *user_data) } } - // `test_mongoc_usleep_custom` tests a custom sleep function set in // `mongoc_client_set_usleep_impl` is applied when topology scanning sleeps. static void @@ -104,6 +90,5 @@ test_mongoc_usleep_custom (void) void test_usleep_install (TestSuite *suite) { - TestSuite_Add (suite, "/Sleep/basic", test_mongoc_usleep_basic); TestSuite_AddMockServerTest (suite, "/Sleep/custom", test_mongoc_usleep_custom); } diff --git a/src/libmongoc/tests/test-mongoc-with-transaction.c b/src/libmongoc/tests/test-mongoc-with-transaction.c index fb5205993b..30c91fd6ac 100644 --- a/src/libmongoc/tests/test-mongoc-with-transaction.c +++ b/src/libmongoc/tests/test-mongoc-with-transaction.c @@ -2,6 +2,8 @@ #include +#include + #include #include #include @@ -17,7 +19,7 @@ with_transaction_fail_transient_txn (mongoc_client_session_t *session, void *ctx BSON_UNUSED (ctx); BSON_UNUSED (error); - _mongoc_usleep (session->with_txn_timeout_ms * 1000); + mlib_sleep_for (session->with_txn_timeout_ms, ms); *reply = bson_new (); BSON_APPEND_ARRAY_BUILDER_BEGIN (*reply, "errorLabels", &labels); diff --git a/src/libmongoc/tests/unified/operation.c b/src/libmongoc/tests/unified/operation.c index 2535a1acfb..21cbb30507 100644 --- a/src/libmongoc/tests/unified/operation.c +++ b/src/libmongoc/tests/unified/operation.c @@ -27,6 +27,7 @@ #include #include +#include #include @@ -3326,7 +3327,7 @@ operation_wait (test_t *test, operation_t *op, result_t *result, bson_error_t *e bson_iter_init_find (&iter, op->arguments, "ms"); ASSERT (BSON_ITER_HOLDS_INT (&iter)); const int64_t sleep_msec = bson_iter_as_int64 (&iter); - _mongoc_usleep (sleep_msec * 1000); + mlib_sleep_for (sleep_msec, ms); result_from_ok (result); return true; @@ -3626,7 +3627,7 @@ log_filter_hide_server_selection_operation (const mongoc_structured_log_entry_t static void _operation_hidden_wait (test_t *test, entity_t *client, const char *name) { - _mongoc_usleep (WAIT_FOR_EVENT_TICK_MS * 1000); + mlib_sleep_for (WAIT_FOR_EVENT_TICK_MS, ms); // @todo Re-examine this once we have support for connection pools in the unified test // runner. Without pooling, all events we could be waiting on would be coming