Files
Alexander Medvednikov a99210e669 ci: fix the macOS CI failures on macOS 27 (#31289)
* sync.stdatomic: skip the implicit TCC build where the host never selects TCC

On macOS 27 and later V no longer picks TCC without `-cc tcc` (#29745), so
the build without `-cc` used Clang and hit the test's `$compile_error`.
Keep the explicit `-cc tcc` builds on every host.

* driver: drop cached function name patterns that no stage builds anymore

#31237 removed the stage code that joined `'voidptr'`, `'void'` and
`'main'` to names, so cached_declarations_test.v reported `*void`, `main*`
and `voidptr*` as stale.

* cgen: unwrap the fixed array that thread wait() returns

`handle.wait()` on a `thread [N]T` produced the `_v_ret_Array_fixed_*`
wrapper struct, which fixed-array assignments, comparisons and arguments
passed to memmove/memcmp as the array itself. Clang rejected the C, which
broke vlib/v/flat/source_type_composition_test.v. Yield `.ret_arr`, as a
direct call to the function does.

* fastc: key declared types by source type id in two tests

#31271 changed the declared_types parameters of collect_function_signatures,
collect_interface_method_signatures, fastc_peek_function_type and
fastc_scan_type_bytes to map[u32]bool, and these tests no longer compiled.

* fastc: list only file-backed pages in the chained fixups of TinyCC executables

TinyCC's Mach-O writer lists every page of __DATA in its chained fixups,
including the zero-fill __bss pages. The dyld of macOS 27 maps each listed
page from the file, so those globals started with __LINKEDIT bytes instead
of zeros (#29744): FastC programs and tests panicked or misbehaved there.

The in-process ad-hoc signer, which FastC and the C backend's TinyCC links
use on macOS, now lowers each segment's page_count to the pages that the
file backs, as ld64 does, before it hashes the file. Pages with fixups stay
listed. The FastC link cache key changes, so that no executable linked
before this is restored.

* wasm: create the TMPDIR that the shape and stub audits give their builds

Both suites point TMPDIR at <scratch>/vtmp without creating it. A nested
`v run` that inherits VTMP from `v test` does not create it either, so
clang failed with "unable to make temporary file" in every native
reference build.

* driver: suspend a run job again when a stop arrives as it resumes

After Ctrl-Z, `v run` stops itself with v3_run_bridging_stop still set.
For `peer | v run app`, `bg` continues the job, and the peer's terminal
read sends SIGTTIN to the job right away; that stop reached the driver
before the flag was cleared and was ignored. The driver kept running and
continued the app while the peer stayed stopped, so the shell saw a running
job and its `fg` sent no SIGCONT: the job hung.

The flag now covers only the driver's own copy of the group stop that it
sends for a stop it did not forward. A stop that is forwarded while the app
is still stopped suspends the job again; the app is continued with the
job-control signals blocked, so a later stop reaches it after its SIGCONT.

The PTY test also waits until the shell has seen the background job stop
before it sends `fg`: bash continues only a job that it knows is stopped.
2026-10-28 08:42:27 +03:00
..
2026-07-29 07:23:45 +03:00
2026-10-14 14:31:39 +03:00
2023-11-25 10:02:51 +03:00
2023-11-25 10:02:51 +03:00
2022-11-05 10:46:40 +03:00
2022-11-05 10:46:40 +03:00
2023-11-25 10:02:51 +03:00
2026-09-17 10:47:56 +03:00
2026-09-17 10:47:56 +03:00
2025-06-27 10:29:09 +03:00
2026-10-14 14:31:39 +03:00
2026-09-17 10:47:56 +03:00

Description

sync provides cross platform handling of concurrency primitives.

On FreeBSD, mutexes store native pthread_mutex_t and pthread_rwlock_t values.

On macOS and default pthread platforms, zero-value RwMutex try locks initialize the native lock and wait for concurrent first-use initialization to finish before acquiring it.

Channels can carry function values, including aliases such as type Task = fn (). Function channels support explicit capacities and default initialization in structs and globals. Arrays of channels can use an explicit init expression to initialize each channel. A received function value can be called normally.

Barrier

new_barrier(n) creates a reusable rendezvous for a positive, fixed number of participants. Share the returned pointer between workers. Each participant calls wait() once per round; all wait until the last participant arrives. Exactly one wait returns true for that round, identifying its leader. Other waits return false. A completed round resets the arrival count automatically. With one participant, every wait returns immediately with true.

Timers

Use a timer when a wait needs to participate in a select:

import sync
import time

timer := sync.new_timer(500 * time.millisecond)
defer {
	timer.stop()
}
select {
	fired_at := <-timer.c {
		println('timer fired at ${fired_at}')
	}
	// another channel can be handled here
}

Concurrent maps

sync.new_concurrent_map[K, V]() creates a map with 32 independently locked shards. Use store, load, load_or_store, and delete for concurrent access. Key types follow builtin maps; pointer keys use pointer identity even when their pointed-to data changes. load_or_store atomically returns the existing value (with true) or stores its argument (with false). len visits shards separately and may reflect concurrent changes. range snapshots each shard before calling the callback, so callbacks can mutate the map. It visits each snapshot entry until the callback returns false; concurrent additions and removals may or may not be reflected. Locks protect map operations, not objects stored as values.

Phaser and StampedLock

new_phaser() creates a reusable barrier. Register each participating worker before the phase starts. Each registered party calls arrive_and_wait() once per phase, or calls arrive_and_deregister() once to leave. The phase advances only when all remaining parties have arrived. Both methods return the phase they arrived in. With no parties, arrival and deregistration leave the phase unchanged. phase_number() observes the phase.

new_stamped_lock() offers shared read_lock()/read_unlock() and exclusive write_lock()/write_unlock() pairs. A try_optimistic_read() stamp is nonzero while no writer is active, and zero during a write. validate(stamp) rejects zero and stamps invalidated by a write. Stamp operations briefly acquire an internal mutex; they do not hold a read lock or wait for a writer to finish. Optimistic access does not make ordinary concurrent reads and writes safe: use atomic data or other synchronization, and validate the stamp after reading. Use a regular read lock for non-atomic shared data. Locks are not reentrant and must be released once for each corresponding acquisition.

Latch

new_latch(n) creates a one-shot countdown gate, with a nonnegative initial count. Each count_down() decrements a positive count. wait() blocks until the count reaches zero; all current and future waiters then pass immediately. Further countdowns do nothing. count() reads the remaining count under the latch's lock. Construct the latch with new_latch, and share the returned pointer between workers.

Parker

Share a new_parker() pointer between a worker and its waker. The worker calls park(), and the waker calls unpark(worker_id) with the worker's sync.thread_id(). An unpark wakes a registered waiter; otherwise it retains one permit for that thread's next park. Repeated unparks before a park coalesce into one permit. A wake consumes the current park's permit, so another unpark may permit the next park even before the worker resumes. Each thread's permits and waits are independent. Keep a Parker scoped to the workers using it: permits for exited threads are not automatically removed.

OnceLock and LazyLock

new_once_lock[T]() creates an initially empty cell. set(value) stores the first value and returns true; subsequent sets return false and keep that value. get() returns none before initialization. get_or_init(f) runs one initializer and returns its value to every caller. All three methods acquire the same mutex, so a get() during initialization waits for the initializer to finish. The initializer must not call any method on the same cell, since that would deadlock.

new_lazy_lock(f) binds an initializer to a cell. Its get() runs the initializer on first use and returns the cached value thereafter. Concurrent calls wait for the same initialization. The initializer must not reenter get() on the same LazyLock.

Construct these cells with their factory functions and share the returned pointers. The locks protect initialization and retrieval; a returned pointer, map, or array may alias the stored data. Synchronize any later mutation of shared returned data separately.