Skip to content

Reference | Resources

olegz edited this page Jun 2, 2026 · 2 revisions

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.

Creating resources

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" 1

Resource.newUnbounded "name" creates a resource that never blocks — useful as a no-op placeholder.

Using resources in rules

requiring — rule-level decorator

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 restore

requiring 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 dbWrite

No extra parentheses needed — => and |> share the same precedence level.

withResource — recipe-level bracket

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.

Dynamic resources — resize

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.Start

resize 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 — available may go negative temporarily, meaning no new acquires succeed until enough releases bring it back up.

No-op for unbounded resources.

Combining with other decorators

requiring composes with other rule-level decorators via |>:

// delegated rule that also requires a dispatch budget
"build:*" => recipe { ... }
    |> requiring dispatchBudget
    |> delegated executor

API summary

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

Low-level primitives

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.

Complete example

See samples/resources.fsx for a runnable script demonstrating all three patterns: download throttling, restore mutex, and DB write serialization.

Clone this wiki locally