From c4a010790ba76732bcf7f8f3d2035e6e9fe2875c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:29 +0300 Subject: [PATCH] all: Optional error parameter for closing channel (fixes #19203) --- doc/docs.md | 8 ++++++++ vlib/builtin/chan_option_result.v | 6 +++--- vlib/sync/channel_opt_propagate_test.v | 12 ++++++++++++ vlib/sync/channels.c.v | 17 ++++++++++++++++- vlib/v/gen/c/cgen.v | 2 +- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index 6b50e1976..b75a832f9 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -4859,6 +4859,8 @@ to do so will then result in a runtime panic (with the exception of `select` and `try_push()` - see below). Attempts to pop will return immediately if the associated channel has been closed and the buffer is empty. This situation can be handled using an `or {}` block (see [Handling options/results](#handling-optionsresults)). +An optional `IError` can be passed to `close(err)` so receive operations that use +`or {}` or `?` can distinguish an error termination from a normal close. ```v wip ch := chan int{} @@ -4872,6 +4874,12 @@ m := <-ch or { // propagate error y := <-ch2 ? + +ch2.close(error('worker failed')) +z := <-ch2 or { + println(err.msg()) + 0.0 +} ``` Note: buffered channels can be closed while they have unread values in them. diff --git a/vlib/builtin/chan_option_result.v b/vlib/builtin/chan_option_result.v index c0084d109..3ee6e0d31 100644 --- a/vlib/builtin/chan_option_result.v +++ b/vlib/builtin/chan_option_result.v @@ -13,9 +13,9 @@ The real implementation is in `vlib/sync/channels.v` */ // close closes the channel for further push transactions. -// closed channels cannot be pushed to, however they can be popped -// from as long as there is still objects available in the channel buffer. -pub fn (ch chan) close() {} +// closed channels cannot be pushed to, and after the buffer is drained +// `<-ch or {}` and `<-ch?` use `err` or default to `channel closed`. +pub fn (ch chan) close(err ...IError) {} // try_pop returns `ChanState.success` if an object is popped from the channel. // try_pop effectively pops from the channel without waiting for objects to become available. diff --git a/vlib/sync/channel_opt_propagate_test.v b/vlib/sync/channel_opt_propagate_test.v index 2e8a66279..63348e91d 100644 --- a/vlib/sync/channel_opt_propagate_test.v +++ b/vlib/sync/channel_opt_propagate_test.v @@ -35,3 +35,15 @@ fn test_channel_array_mut() { sem.wait() assert t == 100 + num_iterations } + +fn test_channel_close_with_error_propagates_after_buffer_drain() { + ch := chan i64{cap: 1} + ch <- i64(7) + ch.close(error('async failure')) + assert <-ch == i64(7) + _ := get_val_from_chan(ch) or { + assert err.msg() == 'async failure' + return + } + assert false +} diff --git a/vlib/sync/channels.c.v b/vlib/sync/channels.c.v index 733a3b528..f944f4f5e 100644 --- a/vlib/sync/channels.c.v +++ b/vlib/sync/channels.c.v @@ -53,6 +53,7 @@ mut: write_sub_mtx &SpinLock read_sub_mtx &SpinLock closed u16 + close_err IError = none pub: cap u32 // queue length in #objects } @@ -118,11 +119,17 @@ fn new_channel_st_noscan(n u32, st u32) &Channel { } } -pub fn (mut ch Channel) close() { +// close closes the channel and optionally stores an error that will be +// returned by receive operations that use `or {}` or `?` after the +// buffered values have been drained. +pub fn (mut ch Channel) close(errs ...IError) { open_val := u16(0) if !C.atomic_compare_exchange_strong_u16(&ch.closed, &open_val, 1) { return } + if errs.len > 0 { + ch.close_err = errs[0] + } mut nulladr := unsafe { nil } for !C.atomic_compare_exchange_weak_ptr(voidptr(&ch.adr_written), voidptr(&nulladr), isize(-1)) { nulladr = unsafe { nil } @@ -149,6 +156,14 @@ pub fn (mut ch Channel) close() { // because we can read from a closed channel later. } +@[inline] +fn (ch &Channel) closed_error() IError { + if ch.close_err !is None__ { + return ch.close_err + } + return error('channel closed') +} + @[inline] pub fn (mut ch Channel) len() int { return int(C.atomic_load_u32(&ch.read_avail)) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 5ef481c0f..a5ef7c97e 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2186,7 +2186,7 @@ fn (mut g Gen) write_chan_pop_option_fns() { static inline ${opt_el_type} __Option_${styp}_popval(${styp} ch) { ${opt_el_type} _tmp = {0}; if (sync__Channel_try_pop_priv(ch, _tmp.data, false)) { - return (${opt_el_type}){ .state = 2, .err = builtin___v_error(_S("channel closed")), .data = {E_STRUCT} }; + return (${opt_el_type}){ .state = 2, .err = sync__Channel_closed_error(ch), .data = {E_STRUCT} }; } return _tmp; }') -- 2.39.5