Skip to content

sync.Mutex.lock() silently ignores pthread_mutex_lock failure — uninitialized (zeroed __global) mutexes provide no exclusion on Darwin #1

Description

@eptx

V fork: sync.Mutex.lock() silently ignores pthread_mutex_lock failure — uninitialized mutexes provide no exclusion (Darwin)

Problem

vlib/sync/sync_default.c.v:

@[inline]
pub fn (mut m Mutex) lock() {
	C.pthread_mutex_lock(&m.mutex)
}

The return value is discarded. On Darwin, pthread_mutex_lock on a
zero-initialized pthread_mutex_t returns EINVAL (Darwin's
PTHREAD_MUTEX_INITIALIZER carries _PTHREAD_MUTEX_SIG_init, not all-zeros),
so a sync.Mutex that was never init()'d — e.g. any VALUE-typed __global,
which _vinit zeroes — becomes a silent no-op lock: no error, no panic, no
mutual exclusion. On Linux/glibc zeroed storage happens to equal
PTHREAD_MUTEX_INITIALIZER, so the same code locks correctly there — the bug
ships only on macOS and presents as an "intermittent flake".

Field impact (cx-private vlang#303)

Two SSE subscriber-registry locks were value-typed __global sync.Mutex.
Under parallel-suite CPU load, two executor threads interleaved inside the
"critical section" and a concurrent array append lost an element (server
trace showed both subscribes printing the same corrupted subs=[10, 0] at the
same millisecond). Root-caused and fixed on the cx side by converting to
&sync.Mutex + sync.new_mutex() (cx-private PR for vlang#303); this issue is the
V-layer footgun that let it fail silently.

Suggested hardening (pick one or more)

  1. Cheapest: assert/panic on nonzero return in lock()/unlock() (debug
    builds at minimum): an EINVAL here is always a programming error and
    currently costs days of intermittent-flake debugging.
  2. -d sync_checked (or default in debug): use PTHREAD_MUTEX_ERRORCHECK
    attrs + checked returns.
  3. Checker warning for VALUE-typed sync.Mutex/sync.RwMutex in __global
    blocks (zeroed in _vinit, unusable on Darwin) — suggest &sync.Mutex +
    sync.new_mutex() in a module init().

Upstream

This is not CX-specific — the discard is in upstream vlib/sync/sync_default.c.v
and bites any V program on macOS. Candidate for the next upstream bundle
(precedent: vlang#27458, vlang#27658): options 1 and 2 are V-only and
CX-agnostic; option 3 (checker warning) likewise. Fix here first, then include
in the bundle rather than filing upstream ad hoc.

Repro sketch

__global ( m sync.Mutex  counter int )
// spawn N threads: m.lock(); counter++; m.unlock()
// macOS: lost updates (lock is a no-op). Linux: correct.

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions