From 7e3657247b128052b36dab7b098fdbd13e50169b Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 22:14:39 +0300 Subject: [PATCH] sync.pool: fix v hangs with parallel http.fetch commands (fixes #10148) --- vlib/net/http/http.v | 25 ++++++++++++++---------- vlib/net/http/request_test.v | 10 ++++++++++ vlib/sync/pool/pool.c.v | 1 + vlib/sync/pool/pool_test.v | 38 ++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/vlib/net/http/http.v b/vlib/net/http/http.v index de235e634..4688e6ea0 100644 --- a/vlib/net/http/http.v +++ b/vlib/net/http/http.v @@ -4,6 +4,7 @@ module http import net.urllib +import time const max_redirects = 16 // safari max - other browsers allow up to 20 @@ -14,16 +15,18 @@ const bufsize = 64 * 1024 // FetchConfig holds configuration data for the fetch function. pub struct FetchConfig { pub mut: - url string - method Method = .get - header Header - data string - params map[string]string - cookies map[string]string - user_agent string = 'v.http' - user_ptr voidptr = unsafe { nil } - verbose bool - proxy &HttpProxy = unsafe { nil } + url string + method Method = .get + header Header + data string + params map[string]string + cookies map[string]string + user_agent string = 'v.http' + user_ptr voidptr = unsafe { nil } + verbose bool + proxy &HttpProxy = unsafe { nil } + read_timeout i64 = 30 * time.second // timeout for reading the response; currently not used for direct https requests + write_timeout i64 = 30 * time.second // timeout for writing the request; currently not used for direct https requests validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid verify string // the path to a rootca.pem file, containing trusted CA certificate(s) @@ -175,6 +178,8 @@ pub fn prepare(config FetchConfig) !Request { user_ptr: config.user_ptr verbose: config.verbose validate: config.validate + read_timeout: config.read_timeout + write_timeout: config.write_timeout verify: config.verify cert: config.cert proxy: config.proxy diff --git a/vlib/net/http/request_test.v b/vlib/net/http/request_test.v index 79bc004f1..6ccc92835 100644 --- a/vlib/net/http/request_test.v +++ b/vlib/net/http/request_test.v @@ -341,3 +341,13 @@ fn test_get_does_not_wait_for_timeout_when_content_length_is_complete() { assert res.body == 'ok' assert elapsed < time.second } + +fn test_prepare_uses_fetch_config_timeouts() { + req := http.prepare( + url: 'http://example.com' + read_timeout: 123 * time.millisecond + write_timeout: 456 * time.millisecond + )! + assert req.read_timeout == 123 * time.millisecond + assert req.write_timeout == 456 * time.millisecond +} diff --git a/vlib/sync/pool/pool.c.v b/vlib/sync/pool/pool.c.v index 0cc365bd8..2d55597a8 100644 --- a/vlib/sync/pool/pool.c.v +++ b/vlib/sync/pool/pool.c.v @@ -87,6 +87,7 @@ pub fn (mut pool PoolProcessor) work_on_pointers(items []voidptr) { njobs = pool.njobs } unsafe { + pool.ntask = 0 pool.thread_contexts = []voidptr{len: items.len} lock pool.results { pool.results = []voidptr{len: items.len} diff --git a/vlib/sync/pool/pool_test.v b/vlib/sync/pool/pool_test.v index bc140b5c7..6a1a39b38 100644 --- a/vlib/sync/pool/pool_test.v +++ b/vlib/sync/pool/pool_test.v @@ -1,4 +1,5 @@ import time +import sync import sync.pool pub struct SResult { @@ -9,6 +10,12 @@ pub struct IResult { i int } +struct SeenContext { +mut: + mutex &sync.Mutex = sync.new_mutex() + seen []int +} + fn worker_s(mut p pool.PoolProcessor, idx int, worker_id int) &SResult { item := p.get_item[string](idx) println('worker_s worker_id: ${worker_id} | idx: ${idx} | item: ${item}') @@ -23,6 +30,15 @@ fn worker_i(mut p pool.PoolProcessor, idx int, worker_id int) &IResult { return &IResult{item * 1000} } +fn worker_reuse(mut p pool.PoolProcessor, idx int, _ int) voidptr { + item := p.get_item[int](idx) + mut ctx := unsafe { &SeenContext(p.get_shared_context()) } + ctx.mutex.lock() + ctx.seen << item + ctx.mutex.unlock() + return pool.no_result +} + fn test_work_on_strings() { mut pool_s := pool.new_pool_processor( callback: worker_s @@ -60,3 +76,25 @@ fn test_work_on_ints() { assert x.i > 100 } } + +fn test_pool_can_be_reused() { + mut ctx := &SeenContext{} + mut pool_i := pool.new_pool_processor( + callback: worker_reuse + maxjobs: 2 + ) + pool_i.set_shared_context(ctx) + pool_i.work_on_items([1, 2, 3]) + ctx.mutex.lock() + mut first_seen := ctx.seen.clone() + ctx.seen = []int{} + ctx.mutex.unlock() + first_seen.sort() + assert first_seen == [1, 2, 3] + pool_i.work_on_items([4, 5]) + ctx.mutex.lock() + mut second_seen := ctx.seen.clone() + ctx.mutex.unlock() + second_seen.sort() + assert second_seen == [4, 5] +} -- 2.39.5