Skip to content

Commit f4ea8d2

Browse files
authored
Merge branch 'main' into repo-assist/release-4.13.0-20260417-b25c6ed2efc2ca62
2 parents 622aa2f + fc443a8 commit f4ea8d2

9 files changed

Lines changed: 267 additions & 112 deletions

File tree

.github/aw/actions-lock.json

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,47 @@
1010
"version": "v9.0.0",
1111
"sha": "d746ffe35508b1917358783b479e04febd2b8f71"
1212
},
13-
"github/gh-aw-actions/[email protected].1": {
13+
"github/gh-aw-actions/[email protected].3": {
1414
"repo": "github/gh-aw-actions/setup",
15-
"version": "v0.68.1",
16-
"sha": "2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc"
15+
"version": "v0.68.3",
16+
"sha": "ba90f2186d7ad780ec640f364005fa24e797b360"
1717
},
18-
"github/gh-aw/actions/[email protected].1": {
18+
"github/gh-aw/actions/[email protected].7": {
1919
"repo": "github/gh-aw/actions/setup",
20-
"version": "v0.68.1",
21-
"sha": "5a06d310cf45161bde77d070065a1e1489fc411c"
20+
"version": "v0.68.7",
21+
"sha": "f916d5de5199f770e46151d455ab1f0288981cc9"
22+
}
23+
},
24+
"containers": {
25+
"ghcr.io/github/gh-aw-firewall/agent:0.25.20": {
26+
"image": "ghcr.io/github/gh-aw-firewall/agent:0.25.20",
27+
"digest": "sha256:9161f2415a3306a344aca34dd671ee69f122317e0a512e66dc64c94b9c508682",
28+
"pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.25.20@sha256:9161f2415a3306a344aca34dd671ee69f122317e0a512e66dc64c94b9c508682"
29+
},
30+
"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20": {
31+
"image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20",
32+
"digest": "sha256:6971639e381e82e45134bcd333181f456df3a52cd6f818a3e3d6de068ff91519",
33+
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20@sha256:6971639e381e82e45134bcd333181f456df3a52cd6f818a3e3d6de068ff91519"
34+
},
35+
"ghcr.io/github/gh-aw-firewall/squid:0.25.20": {
36+
"image": "ghcr.io/github/gh-aw-firewall/squid:0.25.20",
37+
"digest": "sha256:5411d903f73ee597e6a084971c2adef3eb0bd405910df3ed7bf5e3d6bd58a236",
38+
"pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.20@sha256:5411d903f73ee597e6a084971c2adef3eb0bd405910df3ed7bf5e3d6bd58a236"
39+
},
40+
"ghcr.io/github/gh-aw-mcpg:v0.2.19": {
41+
"image": "ghcr.io/github/gh-aw-mcpg:v0.2.19",
42+
"digest": "sha256:44d4d8de7e6c37aaea484eba489940c52df6a0b54078ddcbc9327592d5b3c3dd",
43+
"pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.2.19@sha256:44d4d8de7e6c37aaea484eba489940c52df6a0b54078ddcbc9327592d5b3c3dd"
44+
},
45+
"ghcr.io/github/github-mcp-server:v0.32.0": {
46+
"image": "ghcr.io/github/github-mcp-server:v0.32.0",
47+
"digest": "sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28",
48+
"pinned_image": "ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"
49+
},
50+
"node:lts-alpine": {
51+
"image": "node:lts-alpine",
52+
"digest": "sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f",
53+
"pinned_image": "node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"
2254
}
2355
}
2456
}

.github/workflows/repo-assist.lock.yml

Lines changed: 144 additions & 103 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/repo-assist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ steps:
176176
json.dump(result, f, indent=2)
177177
EOF
178178
179-
source: githubnext/agentics/workflows/repo-assist.md@97143ac59cb3a13ef2a77581f929f06719c7402a
179+
source: githubnext/agentics/workflows/repo-assist.md@11c9a2c442e519ff2b427bf58679f5a525353f76
180180
---
181181

182182
# Repo Assist

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 4.14.0
2+
3+
* Added `AsyncSeq.mapAsyncParallelThrottled` — ordered, bounded-concurrency parallel map. Like `mapAsyncParallel` but limits the number of in-flight operations to `parallelism`, preventing unbounded resource use on large or infinite sequences.
4+
15
### 4.13.0
26

37
* CI: Upgrade Fable from 4.25.0 to 5.0.0-rc.7 and .NET SDK from 8.0.19 to 10.0.100 to fix a CI hang where the Fable build step ran for 6+ hours with .NET 10. Fable 5 + .NET 10 compiles in ~20 seconds.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,28 @@ module AsyncSeq =
11591159
| Choice1Of2 value -> return value
11601160
| Choice2Of2 ex -> return raise ex })
11611161
}
1162+
1163+
let mapAsyncParallelThrottled (parallelism:int) (f:'a -> Async<'b>) (s:AsyncSeq<'a>) : AsyncSeq<'b> = asyncSeq {
1164+
use mb = MailboxProcessor.Start (fun _ -> async.Return())
1165+
use sm = new SemaphoreSlim(parallelism)
1166+
let! err =
1167+
s
1168+
|> iterAsync (fun a -> async {
1169+
do! sm.WaitAsync () |> Async.awaitTaskUnitCancellationAsError
1170+
let! b = Async.StartChild (async {
1171+
try
1172+
let! result = f a
1173+
sm.Release() |> ignore
1174+
return result
1175+
with ex ->
1176+
sm.Release() |> ignore
1177+
return raise ex })
1178+
mb.Post (Some b) })
1179+
|> Async.map (fun _ -> mb.Post None)
1180+
|> Async.StartChildAsTask
1181+
yield!
1182+
replicateUntilNoneAsync (Task.chooseTask (err |> Task.taskFault) (async.Delay mb.Receive))
1183+
|> mapAsync id }
11621184
#endif
11631185

11641186
let chooseAsync f (source:AsyncSeq<'T>) =

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,15 @@ module AsyncSeq =
887887
/// in the order they complete (unordered), without preserving the original order.
888888
val mapAsyncUnorderedParallelThrottled : parallelism:int -> mapping:('T -> Async<'U>) -> s:AsyncSeq<'T> -> AsyncSeq<'U>
889889

890+
/// Builds a new asynchronous sequence whose elements are generated by
891+
/// applying the specified function to all elements of the input sequence,
892+
/// with at most <c>parallelism</c> mapping operations running concurrently.
893+
///
894+
/// The function is applied to elements in order and results are emitted in order,
895+
/// but in parallel, with at most <c>parallelism</c> operations running concurrently.
896+
/// This is the throttled counterpart to <c>mapAsyncParallel</c>.
897+
val mapAsyncParallelThrottled : parallelism:int -> mapping:('T -> Async<'U>) -> s:AsyncSeq<'T> -> AsyncSeq<'U>
898+
890899
/// Applies a key-generating function to each element and returns an async sequence containing unique keys
891900
/// and async sequences containing elements corresponding to the key.
892901
///

src/FSharp.Control.AsyncSeq/FSharp.Control.AsyncSeq.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
</ItemGroup>
2525
<ItemGroup>
2626
<PackageReference Update="FSharp.Core" Version="4.7.2" />
27-
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.5" />
27+
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.6" />
2828
<PackageReference Include="System.Threading.Channels" Version="*" />
2929
<Content Include="*.fsproj; **\*.fs; **\*.fsi;" PackagePath="fable\" />
3030
</ItemGroup>

tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,53 @@ let ``AsyncSeq.mapAsyncUnorderedParallelThrottled should throttle`` () =
19851985

19861986
Assert.AreEqual(50, result.Length)
19871987

1988+
[<Test>]
1989+
let ``AsyncSeq.mapAsyncParallelThrottled should maintain order`` () =
1990+
let ls = List.init 100 id
1991+
let result =
1992+
ls
1993+
|> AsyncSeq.ofList
1994+
|> AsyncSeq.mapAsyncParallelThrottled 5 (fun i -> async {
1995+
do! Async.Sleep (100 - i)
1996+
return i * 2 })
1997+
|> AsyncSeq.toListAsync
1998+
|> Async.RunSynchronously
1999+
Assert.AreEqual(ls |> List.map ((*) 2), result)
2000+
2001+
[<Test>]
2002+
let ``AsyncSeq.mapAsyncParallelThrottled should propagate exception`` () =
2003+
let result =
2004+
AsyncSeq.init 50L id
2005+
|> AsyncSeq.mapAsyncParallelThrottled 5 (fun i -> async {
2006+
if i = 25L then return failwith "test error"
2007+
return i })
2008+
|> AsyncSeq.toListAsync
2009+
|> Async.Catch
2010+
|> Async.RunSynchronously
2011+
match result with
2012+
| Choice2Of2 _ -> ()
2013+
| Choice1Of2 _ -> Assert.Fail("Expected exception")
2014+
2015+
[<Test>]
2016+
let ``AsyncSeq.mapAsyncParallelThrottled should throttle`` () =
2017+
let count = ref 0
2018+
let parallelism = 5
2019+
2020+
let result =
2021+
AsyncSeq.init 50L id
2022+
|> AsyncSeq.mapAsyncParallelThrottled parallelism (fun i -> async {
2023+
let c = Interlocked.Increment count
2024+
if c > parallelism then
2025+
return failwith (sprintf "concurrency exceeded: %d > %d" c parallelism)
2026+
do! Async.Sleep 5
2027+
Interlocked.Decrement count |> ignore
2028+
return i * 2L })
2029+
|> AsyncSeq.toListAsync
2030+
|> Async.RunSynchronously
2031+
2032+
Assert.AreEqual(50, result.Length)
2033+
Assert.AreEqual([ 0L..49L ] |> List.map ((*) 2L), result)
2034+
19882035
//[<Test>]
19892036
//let ``AsyncSeq.mapParallelAsyncBounded should maintain order`` () =
19902037
// let ls = List.init 500 id

version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>4.13.0</Version>
3+
<Version>4.15.0</Version>
44
</PropertyGroup>
55
</Project>

0 commit comments

Comments
 (0)