-
Notifications
You must be signed in to change notification settings - Fork 0
Reference | Resources
Available since Xake 3.2
Resources limit how many rules can use a shared capability at the same time. A bounded resource permits at most N units concurrently; waiters are served FIFO so no rule starves.
Typical uses: throttling network downloads, serializing dotnet restore calls, limiting concurrent database writes.
let downloads = Resource.newResource "net:download" 3 // at most 3 concurrent
let restore = Resource.newResource "dotnet:restore" 1 // mutual exclusion
let dbWrite = Resource.newResource "db:write" 1Resource.newUnbounded "name" creates a resource that never blocks — useful as a no-op placeholder.
The simplest way to attach a resource to a rule. The resource is acquired before the recipe runs and released when it finishes, even on exception. Always acquires 1 unit.
"restore:(project:*)" => recipe {
let! proj = getRuleMatch "project"
do! sh (sprintf "dotnet restore %s" proj) {}
} |> requiring restorerequiring transforms a Rule -> Rule, so it works with any rule syntax:
// with =>
"deploy" => recipe { ... } |> requiring deployLock
// with target
target "out/schema.sql" {
...
} |> requiring dbWrite
// with command
command "migrate" {
...
} |> requiring dbWriteNo extra parentheses needed — => and |> share the same precedence level.
When only part of a recipe needs the resource, or when you need more than 1 unit, use withResource inside the recipe body:
"download:(pkg:*)" => recipe {
let! pkg = getRuleMatch "pkg"
// only the download itself is throttled, not the checksum verification after it
do! withResource downloads 1 (recipe {
do! sh (sprintf "curl -O https://example.com/%s.tar.gz" pkg) {}
})
do! sh (sprintf "sha256sum -c %s.tar.gz.sha256" pkg) {}
}withResource yields the CPU slot while waiting, so blocked rules do not pin worker threads.
The capacity of a bounded resource can change at runtime. This is useful when sharing a worker pool with other agents and the available quota fluctuates:
let workers = Resource.newResource "workers" initialCount
// background loop adjusting capacity from an external coordinator
async {
while true do
do! Async.Sleep 5000
let! quota = queryCoordinatorForQuota()
Resource.resize workers quota
} |> Async.Startresize adjusts the internal available count by the delta between old and new limit:
- Increasing capacity immediately unblocks pending acquires from the FIFO queue.
-
Decreasing capacity lets current holders finish —
availablemay go negative temporarily, meaning no new acquires succeed until enough releases bring it back up.
No-op for unbounded resources.
requiring composes with other rule-level decorators via |>:
// delegated rule that also requires a dispatch budget
"build:*" => recipe { ... }
|> requiring dispatchBudget
|> delegated executor| Function | Level | Description |
|---|---|---|
Resource.newResource name quantity |
creation | Bounded resource, FIFO, quantity >= 1
|
Resource.newUnbounded name |
creation | No-op resource, never blocks |
Resource.resize resource newLimit |
runtime | Change capacity dynamically, newLimit >= 1
|
requiring resource |
rule | Decorator: acquire 1 unit for the entire rule |
withResource resource units body |
recipe | Bracket: acquire N units for a recipe block |
For code outside of recipes — e.g. inside a DelegatedExecutor — use the async-level API:
| Function | Description |
|---|---|
Resource.withAcquired resource units body |
Async bracket: acquire, run body, release |
Resource.acquire resource units |
Acquire only |
Resource.release resource units |
Release only |
These do not touch the CPU slot. Use withAcquired over manual acquire/release to guarantee release on exception.
See samples/resources.fsx for a runnable script demonstrating all three patterns: download throttling, restore mutex, and DB write serialization.