Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,54 @@
|
||||
# x.async examples
|
||||
|
||||
This folder contains short public examples for the current `x.async` API.
|
||||
Each file focuses on one idea and uses only local, in-memory work.
|
||||
|
||||
Examples are documentation-first programs. They show how the public API is
|
||||
expected to be used in small readable scenarios. They are executed by the
|
||||
validation script to avoid bit rot, but the regression guarantees live in the
|
||||
`tests/` folder.
|
||||
|
||||
## Rules
|
||||
|
||||
- No external service is required.
|
||||
- No fixed local path is used.
|
||||
- No network listener is opened.
|
||||
- Examples avoid `panic()` as a control-flow pattern.
|
||||
- Cancellation remains cooperative; examples show jobs checking `ctx.done()`
|
||||
when they may run for more than a tiny unit of work.
|
||||
|
||||
## Examples
|
||||
|
||||
- `basic_group.v`: first error propagation and cooperative sibling
|
||||
cancellation.
|
||||
- `basic_task.v`: one value-returning task consumed with `wait()`.
|
||||
- `worker_pool.v`: fixed concurrency and explicit `try_submit()` backpressure.
|
||||
- `bounded_pool_submit.v`: wait for pool admission with a timeout or context
|
||||
while jobs keep using the pool context.
|
||||
- `periodic.v`: a blocking `every()` loop stopped by context cancellation.
|
||||
- `timeout.v`: one cooperative job bounded by `with_timeout()`.
|
||||
- `net_http/`: synthetic `net.http` request/response processing with `Pool`.
|
||||
- `net_websocket/`: in-memory `websocket.Message` processing. It is not an
|
||||
end-to-end websocket server example.
|
||||
- `mcp/`: in-memory MCP request/response dispatch with `Task[T]`.
|
||||
- `veb/`: synthetic `veb.Context` response lifecycle with `Task[T]`.
|
||||
|
||||
## Run
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
./v run vlib/x/async/examples/basic_group.v
|
||||
./v run vlib/x/async/examples/basic_task.v
|
||||
./v run vlib/x/async/examples/worker_pool.v
|
||||
./v run vlib/x/async/examples/bounded_pool_submit.v
|
||||
./v run vlib/x/async/examples/periodic.v
|
||||
./v run vlib/x/async/examples/timeout.v
|
||||
./v run vlib/x/async/examples/net_http/request_batch.v
|
||||
./v run vlib/x/async/examples/net_websocket/message_pipeline.v
|
||||
./v run vlib/x/async/examples/mcp/tool_dispatch.v
|
||||
./v run vlib/x/async/examples/veb/app_lifecycle.v
|
||||
```
|
||||
|
||||
Use `tools/validate.sh` for the guarded module validation path. It isolates V
|
||||
temporary/cache directories and keeps validation runners serialized.
|
||||
@@ -0,0 +1,30 @@
|
||||
import context
|
||||
import time
|
||||
import x.async as xasync
|
||||
|
||||
fn main() {
|
||||
mut group := xasync.new_group(context.background())
|
||||
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
time.sleep(20 * time.millisecond)
|
||||
return error('stop group')
|
||||
})!
|
||||
|
||||
group.go(fn (mut ctx context.Context) ! {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return ctx.err()
|
||||
}
|
||||
1 * time.second {
|
||||
return error('worker did not observe cancellation')
|
||||
}
|
||||
}
|
||||
})!
|
||||
|
||||
group.wait() or {
|
||||
println('group failed: ${err.msg()}')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import context
|
||||
import x.async as xasync
|
||||
|
||||
fn main() {
|
||||
mut task := xasync.run[int](fn (mut ctx context.Context) !int {
|
||||
_ = ctx
|
||||
return 42
|
||||
})!
|
||||
|
||||
value := task.wait()!
|
||||
println('task result: ${value}')
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import context
|
||||
import time
|
||||
import x.async as xasync
|
||||
|
||||
fn main() {
|
||||
mut pool := xasync.new_pool(workers: 1, queue_size: 1)!
|
||||
started := chan bool{cap: 2}
|
||||
release := chan bool{cap: 2}
|
||||
accepted := chan bool{cap: 1}
|
||||
ran := chan bool{cap: 1}
|
||||
context_ran := chan bool{cap: 1}
|
||||
blocking_job := fn [started, release] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
started <- true
|
||||
_ := <-release
|
||||
}
|
||||
|
||||
pool.try_submit(blocking_job)!
|
||||
if !wait_bool(started) {
|
||||
eprintln('first pool job did not start')
|
||||
exit(1)
|
||||
}
|
||||
pool.try_submit(blocking_job)!
|
||||
|
||||
submitter := spawn fn [mut pool, accepted, ran] () {
|
||||
pool.submit_with_timeout(1 * time.second, fn [ran] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
ran <- true
|
||||
}) or {
|
||||
eprintln('bounded submit failed: ${err.msg()}')
|
||||
accepted <- false
|
||||
return
|
||||
}
|
||||
accepted <- true
|
||||
}()
|
||||
|
||||
release <- true
|
||||
if !wait_bool(accepted) {
|
||||
eprintln('bounded submit was not accepted after capacity opened')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
release <- true
|
||||
if !wait_bool(ran) {
|
||||
eprintln('bounded submit job did not run')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
parent_ctx, cancel := xasync.with_cancel()
|
||||
defer {
|
||||
cancel()
|
||||
}
|
||||
pool.submit_with_context(parent_ctx, fn [context_ran] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
context_ran <- true
|
||||
})!
|
||||
if !wait_bool(context_ran) {
|
||||
eprintln('context-bounded submit job did not run')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
pool.close()!
|
||||
submitter.wait()
|
||||
println('timeout and context bounded submits completed')
|
||||
}
|
||||
|
||||
fn wait_bool(ch chan bool) bool {
|
||||
select {
|
||||
ok := <-ch {
|
||||
return ok
|
||||
}
|
||||
1 * time.second {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# mcp integration example
|
||||
|
||||
This example uses MCP JSON-RPC values in memory and runs the dispatch step in an
|
||||
`x.async.Task`. It does not start stdio, HTTP, a process transport, or any
|
||||
external service.
|
||||
|
||||
The goal is to show the integration boundary:
|
||||
|
||||
- build an MCP request value;
|
||||
- decode and validate it inside a task;
|
||||
- return an MCP response value;
|
||||
- consume the result with `Task.wait()`.
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```sh
|
||||
./v run vlib/x/async/examples/mcp/tool_dispatch.v
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
import context
|
||||
import mcp
|
||||
import x.async as xasync
|
||||
|
||||
struct EchoArgs {
|
||||
pub:
|
||||
text string
|
||||
}
|
||||
|
||||
fn dispatch_tool(raw string) !mcp.Response {
|
||||
req := mcp.decode_request(raw)!
|
||||
if req.method != 'tools/call' {
|
||||
return error('unsupported MCP method')
|
||||
}
|
||||
args := req.decode_params[EchoArgs]()!
|
||||
result := mcp.tool_text_result('echo: ${args.text}')
|
||||
return mcp.new_response(1, result, mcp.ResponseError{})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
request := mcp.new_request(1, 'tools/call', EchoArgs{
|
||||
text: 'hello'
|
||||
})
|
||||
mut task := xasync.run[mcp.Response](fn [request] (mut ctx context.Context) !mcp.Response {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return ctx.err()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
return dispatch_tool(request.encode())!
|
||||
})!
|
||||
|
||||
response := task.wait()!
|
||||
println(response.encode())
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# net.http integration example
|
||||
|
||||
This example shows how `x.async` can coordinate local `net.http` request and
|
||||
response handling without opening a network listener.
|
||||
|
||||
It is intentionally synthetic: requests are constructed in memory, processed by
|
||||
a small handler, and drained through a `Pool`. This keeps the example stable
|
||||
while still showing the lifecycle pattern used by HTTP-facing code:
|
||||
|
||||
- construct or receive a `http.Request`;
|
||||
- submit bounded work to an `x.async.Pool`;
|
||||
- publish a `http.Response` result;
|
||||
- close the pool and report the first error if one occurred.
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```sh
|
||||
./v run vlib/x/async/examples/net_http/request_batch.v
|
||||
```
|
||||
|
||||
No external service, fixed port, file path, or real HTTP server is used.
|
||||
@@ -0,0 +1,47 @@
|
||||
import context
|
||||
import net.http
|
||||
import x.async as xasync
|
||||
|
||||
fn synthetic_http_handler(req http.Request) !http.Response {
|
||||
path := if req.url.contains('?') { req.url.all_before('?') } else { req.url }
|
||||
match path {
|
||||
'/health' {
|
||||
return http.new_response(status: .ok, body: 'healthy')
|
||||
}
|
||||
'/ready' {
|
||||
return http.new_response(status: .ok, body: 'ready')
|
||||
}
|
||||
else {
|
||||
return http.new_response(status: .not_found, body: 'missing')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
requests := [
|
||||
http.new_request(.get, '/health', ''),
|
||||
http.new_request(.get, '/ready', ''),
|
||||
http.new_request(.get, '/missing', ''),
|
||||
]
|
||||
responses := chan string{cap: requests.len}
|
||||
mut pool := xasync.new_pool(workers: 2, queue_size: requests.len)!
|
||||
|
||||
for req in requests {
|
||||
pool.try_submit(fn [req, responses] (mut ctx context.Context) ! {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return ctx.err()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
resp := synthetic_http_handler(req)!
|
||||
responses <- '${req.url} -> ${resp.status_code} ${resp.body}'
|
||||
})!
|
||||
}
|
||||
|
||||
pool.close()!
|
||||
for _ in 0 .. requests.len {
|
||||
println(<-responses)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# net.websocket integration example
|
||||
|
||||
This example stays in memory around `websocket.Message` processing. It does not
|
||||
open a websocket server, does not connect a client, and must not be read as an
|
||||
end-to-end websocket server validation.
|
||||
|
||||
That boundary is deliberate. In this V snapshot, `websocket.Server.close()` is
|
||||
not a complete, stable server shutdown mechanism suitable for fragile
|
||||
validation. The public example therefore demonstrates the safe part that
|
||||
`x.async` can own here: coordinating message/callback-style work and propagating
|
||||
errors without inventing a websocket runtime.
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```sh
|
||||
./v run vlib/x/async/examples/net_websocket/message_pipeline.v
|
||||
```
|
||||
|
||||
No external service, fixed port, or websocket server lifecycle is used.
|
||||
@@ -0,0 +1,49 @@
|
||||
import context
|
||||
import net.websocket
|
||||
import x.async as xasync
|
||||
|
||||
fn describe_message(msg websocket.Message) !string {
|
||||
return match msg.opcode {
|
||||
.text_frame {
|
||||
'text:${msg.payload.bytestr()}'
|
||||
}
|
||||
.ping {
|
||||
'ping'
|
||||
}
|
||||
else {
|
||||
error('unsupported websocket message opcode')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
messages := [
|
||||
websocket.Message{
|
||||
opcode: .text_frame
|
||||
payload: 'hello'.bytes()
|
||||
},
|
||||
websocket.Message{
|
||||
opcode: .ping
|
||||
},
|
||||
]
|
||||
processed := chan string{cap: messages.len}
|
||||
mut group := xasync.new_group(context.background())
|
||||
|
||||
for msg in messages {
|
||||
group.go(fn [msg, processed] (mut ctx context.Context) ! {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return ctx.err()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
processed <- describe_message(msg)!
|
||||
})!
|
||||
}
|
||||
|
||||
group.wait()!
|
||||
for _ in 0 .. messages.len {
|
||||
println(<-processed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import context
|
||||
import time
|
||||
import x.async as xasync
|
||||
|
||||
fn main() {
|
||||
ctx, cancel := xasync.with_cancel()
|
||||
ticks := chan int{cap: 4}
|
||||
done := chan string{cap: 1}
|
||||
|
||||
worker := spawn fn [ctx, ticks, done] () {
|
||||
xasync.every(ctx, 10 * time.millisecond, fn [ticks] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
ticks <- 1
|
||||
}) or {
|
||||
done <- err.msg()
|
||||
return
|
||||
}
|
||||
done <- 'completed'
|
||||
}()
|
||||
|
||||
if !wait_tick(ticks) || !wait_tick(ticks) {
|
||||
eprintln('periodic job did not tick')
|
||||
exit(1)
|
||||
}
|
||||
cancel()
|
||||
|
||||
select {
|
||||
msg := <-done {
|
||||
println('periodic stopped: ${msg}')
|
||||
}
|
||||
1 * time.second {
|
||||
eprintln('periodic job did not stop')
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
worker.wait()
|
||||
}
|
||||
|
||||
fn wait_tick(ticks chan int) bool {
|
||||
select {
|
||||
_ := <-ticks {
|
||||
return true
|
||||
}
|
||||
1 * time.second {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import context
|
||||
import time
|
||||
import x.async as xasync
|
||||
|
||||
fn main() {
|
||||
xasync.with_timeout(20 * time.millisecond, fn (mut ctx context.Context) ! {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return ctx.err()
|
||||
}
|
||||
1 * time.second {
|
||||
return error('work unexpectedly finished')
|
||||
}
|
||||
}
|
||||
}) or {
|
||||
println('timeout result: ${err.msg()}')
|
||||
return
|
||||
}
|
||||
|
||||
eprintln('expected timeout error')
|
||||
exit(1)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# veb integration example
|
||||
|
||||
This example is a compile-time, in-memory `veb.Context` lifecycle sample. It
|
||||
does not start a `veb` server and does not bind a port.
|
||||
|
||||
The example is intentionally small because robust server lifecycle validation
|
||||
belongs to `veb` itself. Here, `x.async` only coordinates a synthetic handler
|
||||
step and returns the response body through `Task.wait()`.
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```sh
|
||||
./v run vlib/x/async/examples/veb/app_lifecycle.v
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
import context
|
||||
import veb
|
||||
import x.async as xasync
|
||||
|
||||
fn render_health_response() !string {
|
||||
mut web_ctx := veb.Context{}
|
||||
_ := web_ctx.text('veb ok')
|
||||
if web_ctx.res.status_code != 200 {
|
||||
return error('unexpected veb status')
|
||||
}
|
||||
return web_ctx.res.body
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut task := xasync.run[string](fn (mut ctx context.Context) !string {
|
||||
done := ctx.done()
|
||||
select {
|
||||
_ := <-done {
|
||||
return ctx.err()
|
||||
}
|
||||
else {}
|
||||
}
|
||||
return render_health_response()!
|
||||
})!
|
||||
|
||||
println(task.wait()!)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import context
|
||||
import time
|
||||
import x.async as xasync
|
||||
|
||||
fn main() {
|
||||
mut pool := xasync.new_pool(workers: 1, queue_size: 1)!
|
||||
started := chan int{cap: 2}
|
||||
release := chan bool{cap: 2}
|
||||
job := fn [started, release] (mut ctx context.Context) ! {
|
||||
_ = ctx
|
||||
started <- 1
|
||||
_ := <-release
|
||||
}
|
||||
|
||||
pool.try_submit(job)!
|
||||
if !wait_started(started) {
|
||||
eprintln('pool job did not start')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
pool.try_submit(job)!
|
||||
pool.try_submit(job) or { println('backpressure: ${err.msg()}') }
|
||||
|
||||
release <- true
|
||||
if !wait_started(started) {
|
||||
eprintln('pool job did not start')
|
||||
exit(1)
|
||||
}
|
||||
release <- true
|
||||
|
||||
pool.close()!
|
||||
println('pool drained')
|
||||
}
|
||||
|
||||
fn wait_started(started chan int) bool {
|
||||
select {
|
||||
_ := <-started {
|
||||
return true
|
||||
}
|
||||
1 * time.second {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user