Skip to content

sync: use SpinLock for channel - #24802

Merged
spytheman merged 2 commits into
vlang:masterfrom
kbkpbot:fix-channel-spin-lock
Jun 28, 2025
Merged

spytheman merged 2 commits into
vlang:masterfrom
kbkpbot:fix-channel-spin-lock

Conversation

@kbkpbot

@kbkpbot kbkpbot commented Jun 27, 2025 •

Copy link
Copy Markdown
Contributor
  1. Use SpinLock for channel, replace some atomic operations.
  2. Fix issue channel has race condition #24680, avoid false error report from valgrind, this will may debugging multi-thread app easier.

BTW, I found that it can read from a closed channel, is this a correct behavior?

@huly-for-github

Copy link
Copy Markdown

Connected to Huly®: V_0.6-23216

@kbkpbot kbkpbot linked an issue Jun 27, 2025 that may be closed by this pull request
@spytheman

Copy link
Copy Markdown
Contributor

Can you please run the benchmarks in vlib/sync/bench/ and compare them between master and this PR?

@spytheman

spytheman commented Jun 27, 2025 •

Copy link
Copy Markdown
Contributor

BTW, I found that it can read from a closed channel, is this a correct behavior?

Not sure ... If we do not have a test that explicitly checks, I am inclined to say no.
Does Go allow it?

edit: after the examples below, I am convinced that it is correct behavior.

@spytheman

Copy link
Copy Markdown
Contributor

I think that reading from a closed channel, that still has buffered items, until they are drained, is useful.
Consider the following example:

import log
import time
import rand

fn writer(ch chan int) {
        for {
                res := <-ch or { break }
                log.warn('${' ':50} saving result: ${res}')
                time.sleep(50 * time.millisecond)
        }
        log.warn('${' ':50} channel was closed, the writer loop broke, writer thread is ending')
}

fn worker(ch chan int, i int) {
        log.debug('>> worker ${i} starts')
        for _ in 0 .. 2 {
                time.sleep((15 + rand.intn(100) or { 0 }) * time.millisecond)
                x := rand.intn(10) or { 0 }
                value := i * 1000_000 + x
                log.debug('>> worker ${i} is sending value: ${value}')
                ch <- value
        }
        log.debug('>> worker ${i} ends')
}

fn main() {
        nthreads := arguments()[1] or { '5' }.int()
        log.set_level(.debug)
        log.info('main start, nthreads: ${nthreads}')
        ch := chan int{cap: 10}
        consumer := spawn writer(ch)
        log.info('consumer started.')
        mut producers := []thread{cap: nthreads}
        for i in 0 .. nthreads {
                producers << spawn worker(ch, i + 1)
        }
        log.info('workers started.')
        producers.wait()
        log.info('workers finished.')
        ch.close()
        log.info('channel closed.')
        consumer.wait()
        log.info('main done, consumer finished')
}

@spytheman

Copy link
Copy Markdown
Contributor

Producing:
image

Notice, how the channel was closed, but the items that were already send in it, were not lost, and the slow writer could still process them.

@jorgeluismireles

Copy link
Copy Markdown
Contributor

Does Go allow it?

@spytheman Go permits to read from a closed channel, since permit to read from the closed channel the elements so far there with len(data) and the with for i := range data { extract the elements.

package main

import (
    "fmt"
)

func main() {
    data := make(chan int, 10)
    end := make(chan bool)
    go write(data, end)
    <- end
    go read(data, end)
    <- end
}

func write(data chan int, end chan bool) {
    for i := 0; i < 5; i++ {
        data <- i
    }
    close(data)
    fmt.Println("closed")
    end <- true
}

func read(data chan int, end chan bool) {
    fmt.Printf("Pending to read = %v\n", len(data))
    for i := range data {
        fmt.Printf("read %v\n", i)
    }
    end <- true
}
$ go run read_closed.go
closed
Pending to read = 5
read 0
read 1
read 2
read 3
read 4

I'm still trying to make a V equivalent...

@jorgeluismireles

Copy link
Copy Markdown
Contributor

In V also we can read from a closed channel. We call reader after channel was closed and inside if select we can read the pending and use closed branch to stop. The program also works if we don't close the channel inside write.

module main

fn main() {

	data := chan int{ cap:10 }
	end := chan bool{}

	spawn write(data, end)
	_ := <- end

	spawn read(data, end)
	_ := <- end
}

fn write(data chan int, end chan bool) {
	for i := 0; i < 5; i++ {
		data <- i
	}
	data.close()
	println('writer: data closed')
	end <- true
}

fn read(data chan int, end chan bool) {
	for {
		if select {
			d := <- data { 
				println('read: d = ${d}')
			}				
		} {
			println('read: open')
		} else {
			println('read: closed')
			break
		}
	}
	end <- true
}
$ v run read_closed.v
writer: data closed
read: d = 0
read: open
read: d = 1
read: open
read: d = 2
read: open
read: d = 3
read: open
read: d = 4
read: open
read: closed

Sounds weird but seems to work.

@kbkpbot

kbkpbot commented Jun 28, 2025

Copy link
Copy Markdown
Contributor Author

I add a run_bench.v to help running benchmark.

v run run_bench.v

And this will generate a result.md at current dir.

compile cmd is something like this:

v channel_bench_v.v -cc clang

Some results on my system(objs/µs, higher is better):

before this PR:

nsend nrec buflen V (tcc ) V (clang) V (gcc )
1 1 0 3.18 3.51 3.72
1 1 100 2.37 2.35 2.38
4 4 0 1.59 1.63 1.61
4 4 100 2.26 2.73 2.66

after apply this PR:

nsend nrec buflen V (tcc ) V (clang) V (gcc )
1 1 0 3.17 3.85 3.85
1 1 100 2.67 2.72 2.68
4 4 0 1.6 1.63 1.54
4 4 100 2.77 2.81 2.64

My system:

V full version V 0.4.11 6b45931.d1d43ab
OS linux, Ubuntu 24.04.2 LTS
Processor 8 cpus, 64bit, little endian, Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
Memory 9.58GB/15.51GB
V executable /media/HD/github/kbkpbot/v/v
V last modified time 2025-06-27 13:18:51
V home dir OK, value: /media/HD/github/kbkpbot/v
VMODULES OK, value: /home/mars/.vmodules
VTMP OK, value: /tmp/v_1000
Current working dir OK, value: /media/HD/github/kbkpbot/v/vlib/sync/bench
Git version git version 2.43.0
V git status weekly.2025.17-319-ga512709e
.git/config present true
cc version cc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
gcc version gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
clang version Ubuntu clang version 18.1.3 (1ubuntu1)
tcc version tcc version 0.9.28rc 2025-02-13 HEAD@f8bd136d (x86_64 Linux)
tcc git status thirdparty-linux-amd64 696c1d84
emcc version N/A
glibc version ldd (Ubuntu GLIBC 2.39-0ubuntu8.4) 2.39

@kbkpbot

kbkpbot commented Jun 28, 2025

Copy link
Copy Markdown
Contributor Author

remove gc, -gc none, has no impact on the channel performance.

@spytheman

Copy link
Copy Markdown
Contributor

Thank you.

@spytheman

spytheman commented Jun 28, 2025 •

Copy link
Copy Markdown
Contributor

Good work.


Fix issue channel has race condition #24680, avoid false error report from valgrind, this will may debugging multi-thread app easier.

I tried to run both --tool=helgrind and --tool=drd for the example in the issue on master and here, and get similar results for gcc:

gcc results
#0 08:39:50 ^ master ~/v>./v version
V 0.4.11 81e84a6
#0 08:39:56 ^ master ~/v>
#0 08:39:58 ^ master ~/v>cat /home/delian/code/misc/2025_06_28__08/tt.v
module main

fn main() {
        ch := chan int{cap: 10}
        expected := 42

        spawn fn [expected, ch] () {
                ch <- expected
        }()

        received := <-ch

        ch.close()

        dump(received)
}
#0 08:40:09 ^ master ~/v>./v /home/delian/code/misc/2025_06_28__08/tt.v -keepc -cg -gc none -d valgrind -cc gcc-10 
#0 08:40:33 ^ master ~/v>valgrind --tool=helgrind /home/delian/code/misc/2025_06_28__08/tt 
==3204763== Helgrind, a thread error detector
==3204763== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==3204763== Using Valgrind-3.19.0.GIT and LibVEX; rerun with -h for copyright info
==3204763== Command: /home/delian/code/misc/2025_06_28__08/tt
==3204763== 
==3204763== ---Thread-Announcement------------------------------------------
==3204763== 
==3204763== Thread #1 is the program's root thread
==3204763== 
==3204763== ---Thread-Announcement------------------------------------------
==3204763== 
==3204763== Thread #2 was created
==3204763==    at 0x49FA342: clone (clone.S:71)
==3204763==    by 0x48BF2EB: create_thread (createthread.c:101)
==3204763==    by 0x48C0E0F: pthread_create@@GLIBC_2.2.5 (pthread_create.c:817)
==3204763==    by 0x4847372: pthread_create_WRK (hg_intercepts.c:445)
==3204763==    by 0x4848880: pthread_create@* (hg_intercepts.c:478)
==3204763==    by 0x41F680: main__main (tt.tmp.c:9892)
==3204763==    by 0x41F9F7: main (tt.tmp.c:9943)
==3204763== 
==3204763== ----------------------------------------------------------------
==3204763== 
==3204763== Possible data race during read of size 2 at 0x4AD0150 by thread #1
==3204763== Locks held: none
==3204763==    at 0x484A7D9: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1118)
==3204763==    by 0x41E28F: sync__Channel_try_pop_priv (tt.tmp.c:9549)
==3204763==    by 0x408640: __chan_int_popval (tt.tmp.c:3397)
==3204763==    by 0x41F6D5: main__main (tt.tmp.c:9897)
==3204763==    by 0x41F9F7: main (tt.tmp.c:9943)
==3204763== 
==3204763== This conflicts with a previous write of size 2 by thread #2
==3204763== Locks held: none
==3204763==    at 0x484A7DC: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1118)
==3204763==    by 0x41DE25: sync__Channel_try_push_priv (tt.tmp.c:9437)
==3204763==    by 0x408680: __chan_int_pushval (tt.tmp.c:3402)
==3204763==    by 0x408B82: anon_fn_42ea68c1dc3dc3e7__116 (tt.tmp.c:3716)
==3204763==    by 0x40896D: anon_fn_void__thread_wrapper (tt.tmp.c:3677)
==3204763==    by 0x484756A: mythread_wrapper (hg_intercepts.c:406)
==3204763==    by 0x48C0608: start_thread (pthread_create.c:477)
==3204763==    by 0x49FA352: clone (clone.S:95)
==3204763==  Address 0x4ad0150 is 0 bytes inside a block of size 40 alloc'd
==3204763==    at 0x483E8D3: malloc (vg_replace_malloc.c:381)
==3204763==    by 0x411FCC: _v_malloc (tt.tmp.c:6261)
==3204763==    by 0x41D28C: sync__new_channel_st (tt.tmp.c:9213)
==3204763==    by 0x41F5D1: main__main (tt.tmp.c:9880)
==3204763==    by 0x41F9F7: main (tt.tmp.c:9943)
==3204763==  Block was alloc'd by thread #1
==3204763== 
[/home/delian/code/misc/2025_06_28__08/tt.v:15] received: 42
==3204763== 
==3204763== Use --history-level=approx or =none to gain increased speed, at
==3204763== the cost of reduced accuracy of conflicting-access information
==3204763== For lists of detected and suppressed errors, rerun with: -s
==3204763== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 0 from 0)
#0 08:41:18 ^ master ~/v>
#0 08:41:19 ^ master ~/v>valgrind --tool=drd /home/delian/code/misc/2025_06_28__08/tt        
==3204771== drd, a thread error detector
==3204771== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
==3204771== Using Valgrind-3.19.0.GIT and LibVEX; rerun with -h for copyright info
==3204771== Command: /home/delian/code/misc/2025_06_28__08/tt
==3204771== 
[/home/delian/code/misc/2025_06_28__08/tt.v:15] received: 42
==3204771== 
==3204771== For lists of detected and suppressed errors, rerun with: -s
==3204771== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 31 from 23)
#0 08:41:25 ^ master ~/v>
#0 08:41:27 ^ master ~/v>
#0 08:41:27 ^ master ~/v>
#0 08:41:27 ^ master ~/v>
#0 08:41:28 ^ master ~/v>git switch -
Switched to branch 'fix-channel-spin-lock'
#0 08:41:36 ^ fix-channel-spin-lock ~/v>./v self
V self compiling ...
V built successfully as executable "v".
#0 08:41:43 ^ fix-channel-spin-lock ~/v>./v version
V 0.4.11 a512709
#0 08:41:46 ^ fix-channel-spin-lock ~/v>./v /home/delian/code/misc/2025_06_28__08/tt.v -keepc -cg -gc none -d valgrind -cc gcc-10
#0 08:41:54 ^ fix-channel-spin-lock ~/v>
#0 08:41:55 ^ fix-channel-spin-lock ~/v>valgrind --tool=helgrind /home/delian/code/misc/2025_06_28__08/tt
==3204858== Helgrind, a thread error detector
==3204858== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==3204858== Using Valgrind-3.19.0.GIT and LibVEX; rerun with -h for copyright info
==3204858== Command: /home/delian/code/misc/2025_06_28__08/tt
==3204858== 
==3204858== ---Thread-Announcement------------------------------------------
==3204858== 
==3204858== Thread #1 is the program's root thread
==3204858== 
==3204858== ---Thread-Announcement------------------------------------------
==3204858== 
==3204858== Thread #2 was created
==3204858==    at 0x49FA342: clone (clone.S:71)
==3204858==    by 0x48BF2EB: create_thread (createthread.c:101)
==3204858==    by 0x48C0E0F: pthread_create@@GLIBC_2.2.5 (pthread_create.c:817)
==3204858==    by 0x4847372: pthread_create_WRK (hg_intercepts.c:445)
==3204858==    by 0x4848880: pthread_create@* (hg_intercepts.c:478)
==3204858==    by 0x41F66D: main__main (tt.tmp.c:9871)
==3204858==    by 0x41F9E4: main (tt.tmp.c:9922)
==3204858== 
==3204858== ----------------------------------------------------------------
==3204858==
==3204858== Possible data race during read of size 2 at 0x4AD0150 by thread #1
==3204858== Locks held: none
==3204858==    at 0x484A7D9: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1118)
==3204858==    by 0x41E215: sync__Channel_try_pop_priv (tt.tmp.c:9534)
==3204858==    by 0x408640: __chan_int_popval (tt.tmp.c:3398)
==3204858==    by 0x41F6C2: main__main (tt.tmp.c:9876)
==3204858==    by 0x41F9E4: main (tt.tmp.c:9922)
==3204858==
==3204858== This conflicts with a previous write of size 2 by thread #2
==3204858== Locks held: none
==3204858==    at 0x484A7DC: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1118)
==3204858==    by 0x41DDCE: sync__Channel_try_push_priv (tt.tmp.c:9426)
==3204858==    by 0x408680: __chan_int_pushval (tt.tmp.c:3403)
==3204858==    by 0x408B82: anon_fn_42ea68c1dc3dc3e7__116 (tt.tmp.c:3717)
==3204858==    by 0x40896D: anon_fn_void__thread_wrapper (tt.tmp.c:3678)
==3204858==    by 0x484756A: mythread_wrapper (hg_intercepts.c:406)
==3204858==    by 0x48C0608: start_thread (pthread_create.c:477)
==3204858==    by 0x49FA352: clone (clone.S:95)
==3204858==  Address 0x4ad0150 is 0 bytes inside a block of size 40 alloc'd
==3204858==    at 0x483E8D3: malloc (vg_replace_malloc.c:381)
==3204858==    by 0x411FCC: _v_malloc (tt.tmp.c:6262)
==3204858==    by 0x41D28C: sync__new_channel_st (tt.tmp.c:9214)
==3204858==    by 0x41F5BE: main__main (tt.tmp.c:9859)
==3204858==    by 0x41F9E4: main (tt.tmp.c:9922)
==3204858==  Block was alloc'd by thread #1
==3204858==
[/home/delian/code/misc/2025_06_28__08/tt.v:15] received: 42
==3204858==
==3204858== Use --history-level=approx or =none to gain increased speed, at
==3204858== the cost of reduced accuracy of conflicting-access information
==3204858== For lists of detected and suppressed errors, rerun with: -s
==3204858== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 0 from 0)
#0 08:42:00 ^ fix-channel-spin-lock ~/v>
#0 08:42:01 ^ fix-channel-spin-lock ~/v>valgrind --tool=drd /home/delian/code/misc/2025_06_28__08/tt
==3204866== drd, a thread error detector
==3204866== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
==3204866== Using Valgrind-3.19.0.GIT and LibVEX; rerun with -h for copyright info
==3204866== Command: /home/delian/code/misc/2025_06_28__08/tt
==3204866==
[/home/delian/code/misc/2025_06_28__08/tt.v:15] received: 42
==3204866==
==3204866== For lists of detected and suppressed errors, rerun with: -s
==3204866== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 31 from 23)
#0 08:42:05 ^ fix-channel-spin-lock ~/v>
#0 08:42:06 ^ fix-channel-spin-lock ~/v>
#0 08:42:06 ^ fix-channel-spin-lock ~/v>

With tcc, valgrind --tool=drd /home/delian/code/misc/2025_06_28__08/tt on this PR, shows:

#0 08:44:12 ^ fix-channel-spin-lock ~/v>valgrind --tool=drd /home/delian/code/misc/2025_06_28__08/tt
==3205055== drd, a thread error detector
==3205055== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
==3205055== Using Valgrind-3.19.0.GIT and LibVEX; rerun with -h for copyright info
==3205055== Command: /home/delian/code/misc/2025_06_28__08/tt
==3205055==
[/home/delian/code/misc/2025_06_28__08/tt.v:15] received: 42
==3205055==
==3205055== For lists of detected and suppressed errors, rerun with: -s
==3205055== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 19 from 11)
#0 08:44:23 ^ fix-channel-spin-lock ~/v>

... while the last line was ==3204966== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 19 from 11) on master.

@kbkpbot

kbkpbot commented Jun 28, 2025 •

Copy link
Copy Markdown
Contributor Author

On my system, all ok for tcc, gcc, clang for --tool=drd :

$ ./run.sh
> C compiler cmd: 'clang' '@/tmp/v_1000/ttt.tmp.c.rsp'
> C compiler response file "/tmp/v_1000/ttt.tmp.c.rsp":
  -fwrapv -g -O0 -o '/home/mars/v/bug/channel/ttt' -I "/media/HD/github/kbkpbot/v/thirdparty/stdatomic/nix" -I "/usr/include/valgrind" "/tmp/v_1000/ttt.tmp.c" -std=gnu99 -D_DEFAULT_SOURCE -rdynamic -lpthread
==670135== drd, a thread error detector
==670135== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
==670135== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==670135== Command: ./ttt
==670135==
[ttt.v:15] received: 42
==670135==
==670135== For lists of detected and suppressed errors, rerun with: -s
==670135== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 14 from 10)
mars@mars-QiTianM428-A688:~/v/bug/channel$ vi run.sh
mars@mars-QiTianM428-A688:~/v/bug/channel$ ./run.sh
> C compiler cmd: 'gcc' '@/tmp/v_1000/ttt.tmp.c.rsp'
> C compiler response file "/tmp/v_1000/ttt.tmp.c.rsp":
  -fwrapv -g -no-pie -o '/home/mars/v/bug/channel/ttt' -I "/media/HD/github/kbkpbot/v/thirdparty/stdatomic/nix" -I "/usr/include/valgrind" "/tmp/v_1000/ttt.tmp.c" -std=gnu99 -D_DEFAULT_SOURCE -rdynamic -lpthread
==670179== drd, a thread error detector
==670179== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
==670179== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==670179== Command: ./ttt
==670179==
[ttt.v:15] received: 42
==670179==
==670179== For lists of detected and suppressed errors, rerun with: -s
==670179== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 14 from 10)
mars@mars-QiTianM428-A688:~/v/bug/channel$ cat ttt.v
module main

fn main() {
        ch := chan int{cap: 10}
        expected := 42

        spawn fn [expected, ch] () {
                ch <- expected
        }()

        received := <-ch

        ch.close()

        dump(received)
}

mars@mars-QiTianM428-A688:~/v/bug/channel$ cat run.sh
EXE=ttt
v -cc gcc -gc none -cg -showcc -keepc -d valgrind ${EXE}.v
valgrind --tool=drd ./${EXE}

#v -cc clang -gc none -cg -showcc -keepc -cflags "-fsanitize=thread" ${EXE}.v
#./${EXE}

@spytheman

Copy link
Copy Markdown
Contributor

For me, the benchmark also shows an improvement in throughput for this PR:

master_result.md
nsend nrec buflen V (tcc ) V (clang) V (gcc )
1 1 0 2.53 3.26 3.31
1 1 100 2.21 2.29 2.37
4 4 0 2.36 2.58 2.55
4 4 100 2.19 2.47 2.54
pr_24802_result.md
nsend nrec buflen V (tcc ) V (clang) V (gcc )
1 1 0 2.77 3.53 3.36
1 1 100 2.28 2.62 2.69
4 4 0 2.31 2.61 2.57
4 4 100 2.26 2.65 2.63
v doctor
V full version V 0.4.11 d32969e.81e84a6
OS linux, Ubuntu 20.04.6 LTS
Processor 4 cpus, 64bit, little endian, Intel(R) Core(TM) i3-3225 CPU @ 3.30GHz
Memory 2.64GB/15.05GB
V executable /home/delian/v/v
V last modified time 2025-06-28 06:01:01
V home dir OK, value: /home/delian/v
VMODULES OK, value: /home/delian/.vmodules
VTMP OK, value: /tmp/v_1000
Current working dir OK, value: /home/delian/v/vlib/sync/bench
env VFLAGS "-no-parallel"
Git version git version 2.49.0
V git status weekly.2025.26-20-g81e84a6a
.git/config present true
cc version cc (Ubuntu 10.5.0-1ubuntu1~20.04) 10.5.0
gcc version gcc (Ubuntu 10.5.0-1ubuntu1~20.04) 10.5.0
clang version clang version 10.0.0-4ubuntu1
tcc version tcc version 0.9.28rc 2025-02-13 HEAD@f8bd136d (x86_64 Linux)
tcc git status thirdparty-linux-amd64 696c1d84
emcc version N/A
glibc version ldd (Ubuntu GLIBC 2.31-0ubuntu9.16) 2.31

@spytheman

Copy link
Copy Markdown
Contributor

I should probably update my valgrind, or install a new Ubuntu one of these days 🤔 .

@spytheman

Copy link
Copy Markdown
Contributor

I want to test a bit more, but so far, it works very well.

Excellent work.

@kbkpbot

kbkpbot commented Jun 28, 2025 •

Copy link
Copy Markdown
Contributor Author

I think following lines in try_push_priv() should replace by a SpinLock also:

				mut expected_status := u16(BufferElemStat.unused)
				for !C.atomic_compare_exchange_weak_u16(status_adr, &expected_status,
					u16(BufferElemStat.writing)) {
					expected_status = u16(BufferElemStat.unused)
				}

and try_pop_priv()

				mut expected_status := u16(BufferElemStat.written)
				for !C.atomic_compare_exchange_weak_u16(status_adr, &expected_status,
					u16(BufferElemStat.reading)) {
					expected_status = u16(BufferElemStat.written)
				}

Wait a moment, I will check this.

@kbkpbot

kbkpbot commented Jun 28, 2025

Copy link
Copy Markdown
Contributor Author

After upgrade my valgrind to latest version(valgrind-3.25.1), it report no error :)

$ cat ttt.v
module main

fn main() {
        ch := chan int{cap: 10}
        expected := 42

        spawn fn [expected, ch] () {
                ch <- expected
        }()

        received := <-ch

        ch.close()

        dump(received)
}

mars@mars-QiTianM428-A688:~/v/bug/channel$ cat run.sh
EXE=ttt
v -cc clang -gc none -cg -showcc -keepc -d valgrind ${EXE}.v
valgrind --tool=drd ./${EXE}
valgrind --tool=helgrind ./${EXE}

#v -cc clang -gc none -cg -showcc -keepc -cflags "-fsanitize=thread" ${EXE}.v
#./${EXE}

mars@mars-QiTianM428-A688:~/v/bug/channel$ ./run.sh
> C compiler cmd: 'clang' '@/tmp/v_1000/ttt.tmp.c.rsp'
> C compiler response file "/tmp/v_1000/ttt.tmp.c.rsp":
  -fwrapv -g -O0 -o '/home/mars/v/bug/channel/ttt' -I "/media/HD/github/kbkpbot/v/thirdparty/stdatomic/nix" -I "/usr/include/valgrind" "/tmp/v_1000/ttt.tmp.c" -std=gnu99 -D_DEFAULT_SOURCE -rdynamic -lpthread
==722378== drd, a thread error detector
==722378== Copyright (C) 2006-2024, and GNU GPL'd, by Bart Van Assche et al.
==722378== Using Valgrind-3.25.1 and LibVEX; rerun with -h for copyright info
==722378== Command: ./ttt
==722378==
[ttt.v:15] received: 42
==722378==
==722378== For lists of detected and suppressed errors, rerun with: -s
==722378== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 14 from 10)
==722380== Helgrind, a thread error detector
==722380== Copyright (C) 2007-2024, and GNU GPL'd, by OpenWorks LLP et al.
==722380== Using Valgrind-3.25.1 and LibVEX; rerun with -h for copyright info
==722380== Command: ./ttt
==722380==
[ttt.v:15] received: 42
==722380==
==722380== Use --history-level=approx or =none to gain increased speed, at
==722380== the cost of reduced accuracy of conflicting-access information
==722380== For lists of detected and suppressed errors, rerun with: -s
==722380== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
mars@mars-QiTianM428-A688:~/v/bug/channel$ valgrind --version
valgrind-3.25.1

@kbkpbot

kbkpbot commented Jun 28, 2025

Copy link
Copy Markdown
Contributor Author

I think following lines in try_push_priv() should replace by a SpinLock also:

				mut expected_status := u16(BufferElemStat.unused)
				for !C.atomic_compare_exchange_weak_u16(status_adr, &expected_status,
					u16(BufferElemStat.writing)) {
					expected_status = u16(BufferElemStat.unused)
				}

and try_pop_priv()

				mut expected_status := u16(BufferElemStat.written)
				for !C.atomic_compare_exchange_weak_u16(status_adr, &expected_status,
					u16(BufferElemStat.reading)) {
					expected_status = u16(BufferElemStat.written)
				}

Wait a moment, I will check this.

No need to fix this any more.

@spytheman
spytheman merged commit bd465b5 into vlang:master Jun 28, 2025
@kbkpbot
kbkpbot deleted the fix-channel-spin-lock branch June 28, 2025 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

channel has race condition

3 participants