Skip to content

feat(go): preventing concurrent reads/writes on streams and futures#1490

Merged
dicej merged 1 commit into
bytecodealliance:mainfrom
asteurer:concurrent
Jan 8, 2026
Merged

feat(go): preventing concurrent reads/writes on streams and futures#1490
dicej merged 1 commit into
bytecodealliance:mainfrom
asteurer:concurrent

Conversation

@asteurer

@asteurer asteurer commented Jan 7, 2026

Copy link
Copy Markdown
Contributor

Overview

This change is intended to prevent concurrent reads and writes on streams and futures

Closes #1460

Observations

Given the following guest application code:

func Handle(request *Request) Result[*Response, ErrorCode] {
	tx, rx := MakeStreamU8()

	go func() {
		defer tx.Drop()
		for i := 0; i < 100; i++ {
			tx.Write([]byte(fmt.Sprintf("Hello %d", i)))
			time.Sleep(1 * time.Millisecond)
		}
	}()

	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			buffer := make([]uint8, 7)
			for j := 0; j < 10; j++ {
				// ERROR: READ CONCURRENTLY
				count := rx.Read(buffer)
				if count == 0 {
					break
				}

				fmt.Printf("output: %s", string(buffer))
			}
		}(i)
	}

	wg.Wait()

	return Err[*wasi_http_handler.Response](MakeErrorCodeDestinationIpUnroutable())

}

I would expect there to be a panic and have there be a "nil handle" message; however, this is the output I get:

worker error: error while executing at wasm backtrace:
    0: 0x2916ef - <unknown>!<wasm function 16>
    1: 0x182eb0 - <unknown>!wit_component_wasi_http_types.wasm_stream_read_u8
    2: 0x184933 - <unknown>!github.com_bytecodealliance_wit_bindgen_wit_types.__StreamReader_go.shape.uint8__.Read
    3: 0x1890b5 - <unknown>!wit_component_export_wasi_http_handler.Handle.func2
    4: 0x188d81 - <unknown>!wit_component_export_wasi_http_handler.Handle.gowrap1
    5: 0x12204b - <unknown>!wasm_pc_f_loop_export
    6: 0x18c0d1 - <unknown>!_async_lift_wasi_http_handler_0.3.0_rc_2025_09_16_handle
error: channel closed

This is concerning because the Goroutine appears to be moving past the panic in the wit_runtime.Handle.Take() method:

func (self *Handle) Take() int32 {
	if self.value == 0 {
		panic("nil handle")
	}
	value := self.value
	self.value = 0
	return value
}

@dicej any thoughts and/or suggestions?

@dicej

dicej commented Jan 7, 2026

Copy link
Copy Markdown
Collaborator
time.Sleep(1 * time.Millisecond)

time.Sleep will block the entire runtime since there's no GOOS=wasip3 yet. You'll need to use wasi:clocks@0.3.0-rc- 2025-09-26/monotonic-clock#wait-for instead (i.e. generating bindings from the WASIp3 WIT files and using those directly).

Comment thread crates/go/src/package/wit_types/wit_future.go Outdated
Comment thread crates/go/src/package/wit_types/wit_future.go Outdated
@asteurer asteurer requested a review from dicej January 7, 2026 22:38
@asteurer

asteurer commented Jan 7, 2026

Copy link
Copy Markdown
Contributor Author

@dicej Looks like the CI/Check GH Actions workflow failed. I assume the fix for that is just re-running it? If so, would you mind doing that for me?

@dicej

dicej commented Jan 7, 2026

Copy link
Copy Markdown
Collaborator

@dicej Looks like the CI/Check GH Actions workflow failed. I assume the fix for that is just re-running it? If so, would you mind doing that for me?

👍

BTW, would you be able to add test coverage for this to the tests/runtime-async suite, perhaps by expanding simple-stream-payload?

@asteurer

asteurer commented Jan 8, 2026

Copy link
Copy Markdown
Contributor Author

@dicej When you have a moment, this is ready to be reviewed

@dicej dicej left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks! Just a few doc tweak suggestions.

Comment thread crates/go/src/package/wit_types/wit_future.go Outdated
Comment thread crates/go/src/package/wit_types/wit_future.go Outdated
Comment thread crates/go/src/package/wit_types/wit_stream.go Outdated
Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>

fix: adding panic for empty dst slice, removing handle reassignment for futures

Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>

docs(go): adding package comments

Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>

feat(go): add future read concurrency test

Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>

feat(go): adding remaining concurrent read/write tests

Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>

doc(go): adding comments on drop methods

Signed-off-by: Andrew Steurer <94206073+asteurer@users.noreply.github.com>

Update crates/go/src/package/wit_types/wit_stream.go

Co-authored-by: Joel Dice <joel.dice@fermyon.com>

Update crates/go/src/package/wit_types/wit_future.go

Co-authored-by: Joel Dice <joel.dice@fermyon.com>

Update crates/go/src/package/wit_types/wit_future.go

Co-authored-by: Joel Dice <joel.dice@fermyon.com>
@asteurer asteurer requested a review from dicej January 8, 2026 23:02
@dicej dicej enabled auto-merge January 8, 2026 23:03
@dicej dicej added this pull request to the merge queue Jan 8, 2026
Merged via the queue into bytecodealliance:main with commit 44225ca Jan 8, 2026
27 checks passed
@asteurer asteurer deleted the concurrent branch January 8, 2026 23:33
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.

go: prevent concurrent reads or writes on the same stream or future

2 participants