Skip to content

한국어 | English

Core Spec Index | Previous: Monitoring | Next: Runtime Boundary

Utilities

What this chapter defines — The public contracts of utility APIs that do not belong to an individual category, including atomic counters, timers, stopwatches, capability detection, proxies, and thread helpers.

1. Utilities Overview

zlink Core provides common runtime features that are not part of the messaging contract as utility APIs. These include atomic counters for atomically handling a shared integer, nanosecond-resolution timers, stopwatches as high-resolution clocks, capability detection for checking which features a library build includes, proxies for forwarding messages between two raw sockets, and sleep and thread helpers.

This document defines the public contracts of these utilities. Its intended readers are developers who map each utility's lifecycle, thread safety, and callback ownership to the C API and each language binding. It answers the question, "When using common runtime features, how should the lifetime of each handle and callback, the scope of concurrent calls, and return values be interpreted?"

The following documents own the related contracts.

Related contract Defining document
zlink_poller_add_timer, which registers a timer with a poller Poll and Poller
Raw socket creation and send/receive contracts (the targets forwarded by a proxy) Socket Common
Context lifetime and termination Context

2. Atomic Counter

An atomic counter provides atomic increment, decrement, and read operations on a single integer shared by multiple threads. Create a counter with zlink_atomic_counter_new and destroy it with zlink_atomic_counter_destroy.

Creates a new atomic counter initialized to zero.

ZLINK_EXPORT void *zlink_atomic_counter_new (void);

Allocates and returns an opaque handle to an atomic counter whose initial value is zero.

Returns: A counter handle on success. If memory allocation fails, the process aborts instead of returning NULL.

Thread safety: May be called from any thread.

See also: zlink_atomic_counter_set, zlink_atomic_counter_destroy


Sets the counter to an explicit value.

ZLINK_EXPORT void zlink_atomic_counter_set (void *counter_, int value_);

Replaces the current counter value with value_.

Thread safety: Not thread-safe. It must not be called concurrently with another operation on the same counter. It is typically used only during initial setup.

See also: zlink_atomic_counter_value


Increments the counter by one.

ZLINK_EXPORT int zlink_atomic_counter_inc (void *counter_);

Atomically increments the counter and returns its previous value (the value immediately before the increment).

Returns: The counter value before the increment.

Thread safety: May be called from any thread.

See also: zlink_atomic_counter_dec


Decrements the counter by one.

ZLINK_EXPORT int zlink_atomic_counter_dec (void *counter_);

Atomically decrements the counter and returns 1 if it remains greater than zero after the decrement, or 0 if it reaches zero.

Returns: 1 if the counter is still nonzero after the decrement, or 0 if it has reached zero.

Thread safety: May be called from any thread.

See also: zlink_atomic_counter_inc


Returns the current counter value.

ZLINK_EXPORT int zlink_atomic_counter_value (void *counter_);

Atomically reads the current value of the counter.

Returns: The current counter value.

Thread safety: May be called from any thread.

See also: zlink_atomic_counter_set


Destroys the counter and releases its memory.

ZLINK_EXPORT void zlink_atomic_counter_destroy (void **counter_p_);

Releases the counter handle. After destruction, the pointer at *counter_p_ is set to NULL.

Thread safety: It must not be called while another thread is operating on the same counter.

See also: zlink_atomic_counter_new

3. Timer

A timer provides a nanosecond-resolution periodic or one-shot generic timer. Create a standalone timer with zlink_timer_new. A timer fire event is received with zlink_timer_recv or integrated into a poller with zlink_poller_add_timerPoll and Poller owns the poller integration contract.

%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
    participant App as Application
    participant T as Timer
    App->>T: zlink_timer_new()
    App->>T: zlink_timer_start(interval_ns, repeat_count)
    Note over T: First fire after interval_ns nanoseconds,<br/>then repeats at the same interval
    App->>T: zlink_timer_recv()
    T-->>App: fire count (increments from 1 within this start)
    Note over T: If repeat_count is positive, automatically stops after that many fires
    App->>T: zlink_timer_stop() / zlink_timer_destroy()

Creates a standalone timer.

ZLINK_EXPORT void *zlink_timer_new (void);

Allocates and returns an opaque timer handle. Destroy it with zlink_timer_destroy when it is no longer needed.

Returns: A timer handle on success, or NULL on failure. On failure, errno is set.

Thread safety: May be called from any thread.

See also: zlink_timer_destroy


Destroys a timer and releases its resources.

ZLINK_EXPORT zlink_close_result_t zlink_timer_destroy (void **timer_p_);

Stops a running timer and releases its handle. After destruction, *timer_p_ is set to NULL.

Returns: ZLINK_CLOSE_OK on success, or a zlink_close_result_t value on failure. zlink_errno() preserves the internal errno for diagnostics.

Thread safety: It must not be called while another thread is using the same timer.

See also: zlink_timer_new


Starts a timer.

ZLINK_EXPORT zlink_config_result_t zlink_timer_start (void *timer_,
                                         uint64_t interval_ns_,
                                         uint64_t repeat_count_);

Starts the timer so that its first event occurs after interval_ns_ nanoseconds. interval_ns_ is the interval between events in nanoseconds and must not be 0. If repeat_count_ is 0, the timer repeats until explicitly stopped. If it is positive, the timer generates that many events and then stops automatically. Each successful start resets the fire count, so the first fire is 1, followed by 2, 3, and so on. Calling it again on a running timer also succeeds — the new start replaces the existing schedule with the new interval and repeat count, and discards any fire not yet read and any poller readiness from the previous start.

Returns: ZLINK_CONFIG_OK on success, or a zlink_config_result_t value on failure. zlink_errno() preserves the internal errno for diagnostics.

Errors: If interval_ns_ == 0, the result is ZLINK_CONFIG_INVALID_ARGUMENT and the internal errno is EINVAL.

Thread safety: It must not be called concurrently with another operation on the same timer.

See also: zlink_timer_stop


Stops a running timer.

ZLINK_EXPORT zlink_config_result_t zlink_timer_stop (void *timer_);

Stops the timer. It generates no new fire events until it is started again.

Returns: ZLINK_CONFIG_OK on success, or a zlink_config_result_t value on failure. zlink_errno() preserves the internal errno for diagnostics.

Thread safety: It must not be called concurrently with another operation on the same timer.

See also: zlink_timer_start


Synchronously receives a timer fire.

ZLINK_EXPORT zlink_recv_result_t zlink_timer_recv (void *timer_, uint64_t *fire_count_out_);

In receive mode, waits for the next timer fire. On success, *fire_count_out_ is set to the fire count that starts from 1 within the most recent start execution.

Returns: ZLINK_RECV_OK on success, or a zlink_recv_result_t value on failure. zlink_errno() preserves the internal errno for diagnostics.

Errors: If timer_ is invalid or fire_count_out_ == NULL, the result is ZLINK_RECV_INVALID_HANDLE (internal EFAULT). If the timer has already stopped and there is no fire left to read, the result is ZLINK_RECV_NO_DATA (internal EAGAIN).

Thread safety: It must not be called concurrently with another operation on the same timer.

See also: zlink_timer_start

4. Stopwatch

A stopwatch provides high-resolution timing functions for benchmarking and profiling. Start the stopwatch, read intermediate measurements, and stop it to obtain the total elapsed time in microseconds.

Starts a high-resolution stopwatch.

ZLINK_EXPORT void *zlink_stopwatch_start (void);

Captures the current time and returns an opaque handle used to measure elapsed time. The handle must eventually be released with zlink_stopwatch_stop.

Returns: An opaque stopwatch handle on success. If memory allocation fails, the process aborts instead of returning NULL.

Thread safety: May be called from any thread. The returned handle must be used by only one thread at a time.

See also: zlink_stopwatch_intermediate, zlink_stopwatch_stop


Returns elapsed microseconds without stopping the stopwatch.

ZLINK_EXPORT unsigned long zlink_stopwatch_intermediate (void *watch_);

Reads the elapsed time since zlink_stopwatch_start was called without releasing the handle. It may be called multiple times for successive measurements.

Returns: Elapsed time in microseconds.

Thread safety: It must not be called concurrently with zlink_stopwatch_stop on the same handle.

See also: zlink_stopwatch_start, zlink_stopwatch_stop


Stops the stopwatch and returns the total elapsed microseconds.

ZLINK_EXPORT unsigned long zlink_stopwatch_stop (void *watch_);

Returns the total elapsed time since zlink_stopwatch_start was called and releases the stopwatch handle. The handle must not be used after this call.

Returns: Elapsed time in microseconds.

Thread safety: It must not be called concurrently with another operation on the same handle.

See also: zlink_stopwatch_start, zlink_stopwatch_intermediate

5. Capability Detection

Use zlink_has at runtime to determine which features were included when the library was built.

Checks whether the current library build provides a capability.

ZLINK_EXPORT bool zlink_has (const char *capability_);

capability_ is a non-NULL, NUL-terminated string, and the function does not retain it. "tcp" is always true. "ipc", "tls", "ws", and "wss" are true only when the library was built with the corresponding feature. Any other string is false.

Thread safety: Does not mutate global state and may be called from any thread.

6. Proxy

A proxy is a blocking helper that forwards multipart messages bidirectionally between two raw sockets. Socket Common owns the creation and send/receive contracts of raw sockets.

Forwards multipart messages bidirectionally between two raw sockets.

ZLINK_EXPORT zlink_config_result_t zlink_proxy (void *frontend_, void *backend_, void *capture_);

frontend_ and backend_ are required raw socket handles. capture_ may be NULL; when non-NULL, it is a raw socket handle that receives a copy of every forwarded message. The function blocks the calling thread until the running proxy loop ends.

All three handles are borrowed. The function neither closes nor owns them. The proxy receives message frames and forwards them to the opposite socket without returning frame pointers to the application.

The proxy forwards only raw multipart messages on the application lane. Even if a received message has an internal ZMP request-reply kind and sequence, the proxy removes them before sending the message to the opposite socket and to capture_. Both destinations therefore receive the same application part count, order, and bytes, while a frame sent back to the wire has the ordinary data kind.

The proxy does not bridge request correlation or reply-target state. This API does not transparently complete a request across a proxy, and the proxy neither creates request-reply metadata nor forwards completion records.

Returns: The proxy loop has no normal termination condition and no public stop API, so there is no path on which this function returns ZLINK_CONFIG_OK. When a poll, receive, or send fails — including socket or context termination — the function returns that errno as a zlink_config_result_t error and the calling thread is released. If a required handle is NULL or is not a raw socket, the result is ZLINK_CONFIG_INVALID_HANDLE before the loop starts.

7. Sleep and Thread

These are a portable sleep function that wraps platform-specific APIs and an OS thread helper.

typedef void (zlink_thread_fn) (void *);

This is the entry-point signature of a thread started with zlink_thread_start.


Sleeps for the specified number of seconds.

ZLINK_EXPORT void zlink_sleep (int seconds_);

A convenience wrapper that applies the platform sleep (Sleep() or POSIX sleep()) once to the calling thread. On POSIX, if sleep() is interrupted by a signal the remaining time is not slept again, so the call may return earlier than seconds_ seconds.

Thread safety: May be called from any thread.

See also: zlink_stopwatch_start


Starts a new thread that runs the specified function.

ZLINK_EXPORT void *zlink_thread_start (zlink_thread_fn *func_, void *arg_);

Creates and starts a new operating-system thread that executes func_ with arg_ as its sole argument. The returned handle must be passed to zlink_thread_join to wait for completion and release resources.

Returns: An opaque thread handle on success. If allocation of the handle or creation of the operating-system thread fails, the process aborts instead of returning NULL.

Thread safety: May be called from any thread.

See also: zlink_thread_join


Waits for a thread to finish and releases its handle.

ZLINK_EXPORT void zlink_thread_join (void *thread_);

Blocks the calling thread until the thread identified by thread_ terminates, then releases the handle. The handle must not be used after this call.

Thread safety: Must be called exactly once per handle. It must not be called from the thread being joined.

See also: zlink_thread_start

8. Implementation and Contract Test Verification Requirements

Verify the following using only the public surface (utility functions, return values, and errno). Each item maps to one unit test.

Atomic counter

  • A counter created with zlink_atomic_counter_new has an initial value of zero—zlink_atomic_counter_value returns 0 immediately after creation. If a memory allocation failure is injected, the function aborts the process instead of returning NULL.
  • After zlink_atomic_counter_set, zlink_atomic_counter_value returns the set value.
  • zlink_atomic_counter_inc returns the value immediately before the increment.
  • zlink_atomic_counter_dec returns 1 if the counter remains greater than zero after the decrement, or 0 if it reaches zero.
  • It is safe for multiple threads to call inc, dec, and value concurrently on the same counter—updates are not lost because increments and decrements are atomic.
  • After zlink_atomic_counter_destroy, *counter_p_ is NULL.

Timer

  • zlink_timer_new returns a non-NULL handle on success, or NULL with errno set on failure.
  • zlink_timer_start(timer, 0, repeat_count) fails with ZLINK_CONFIG_INVALID_ARGUMENT and internal EINVAL.
  • When zlink_timer_start receives a positive repeat_count_, the timer fires that many times and then stops automatically. When it receives 0, the timer repeats until explicitly stopped.
  • zlink_timer_recv waits for the next fire and, on success, writes to *fire_count_out_ the fire count that starts from 1 within the current start execution. After a stop followed by another start, the first value is 1 again. If the timer has already stopped and no fire remains to be read, the result is ZLINK_RECV_NO_DATA (internal EAGAIN).
  • After zlink_timer_stop, no new fire event occurs until the timer is started again.
  • After zlink_timer_destroy, *timer_p_ is NULL.

Stopwatch

  • If a memory allocation failure is injected into zlink_stopwatch_start, the function aborts the process instead of returning NULL.
  • zlink_stopwatch_intermediate returns the elapsed microseconds since start without releasing the handle and may be called multiple times with the same handle.
  • zlink_stopwatch_stop returns the total elapsed microseconds since start and releases the handle.

Capability detection

  • zlink_has("tcp") is always true.
  • "ipc", "tls", "ws", and "wss" are true only when the library was built with the corresponding feature, and any other string is false.

Proxy

  • If a required handle passed to zlink_proxy is NULL or is not a raw socket, the result is ZLINK_CONFIG_INVALID_HANDLE.
  • When a non-NULL capture_ is supplied, a copy of every forwarded message arrives at the capture socket.
  • The proxy blocks the calling thread until the loop ends and returns ZLINK_CONFIG_OK when it ends normally.
  • The supplied handles are borrowed—the caller still owns them after the proxy ends, and the function does not close them.
  • When a raw fixture with request, reply, or error-reply kind is sent through the proxy, the opposite socket and a non-NULL capture socket receive the same application multipart as an ordinary message; sending it back to the raw wire produces the data kind.
  • Even though DEALER-ROUTER uses a single connection, the proxy does not bridge request correlation or reply-target state, so a reply on its far side does not automatically complete the original request.

Sleep and thread

  • zlink_sleep(n) suspends the calling thread for at least n seconds.
  • zlink_thread_start starts a thread that executes func_ with arg_ as its sole argument and returns a handle on success. If a handle allocation or operating-system thread creation failure is injected, the function aborts the process instead of returning NULL.
  • zlink_thread_join waits until the target thread terminates and then releases the handle, and it is called exactly once per handle.

Common return convention

  • Each function that returns a result type (zlink_close_result_t, zlink_config_result_t, or zlink_recv_result_t) returns the corresponding OK value on success and a result value on failure, while zlink_errno() preserves the internal errno for diagnostics.

Core Spec Index | Previous: Monitoring | Next: Runtime Boundary