#### Limiters

# Overview

`DexLimiter` bounds the number of asynchronous operations that may run at once. Use it when a workload can produce more parallel work than a service, subsystem, or machine should handle at the same time.

Typical uses include indexing files, downloading URLs, generating thumbnails, parsing documents, or querying a service with a known concurrency budget.

## Running Work

The safest API is `dex_limiter_run()`. It acquires a permit, spawns a fiber, and releases the permit when that fiber completes.

    static DexFuture *
    load_one_file (gpointer user_data)
    {
      GFile *file = user_data;

      return dex_file_load_contents_bytes (file);
    }

    DexLimiter *limiter = dex_limiter_new (8);
    DexFuture *future = dex_limiter_run (limiter,
                                         NULL,
                                         0,
                                         load_one_file,
                                         g_object_ref (file),
                                         g_object_unref);

The returned future resolves or rejects with the result of the spawned fiber. If many calls to `dex_limiter_run()` are made, only the configured number will run at once. Once a fiber has started, dropping the returned future does not stop the fiber; it is allowed to complete so the permit can be released.

Use `dex_limiter_run_on_pool()` when the limited work should run on a `DexThreadPool` instead of a fiber. This is useful for blocking or foreign APIs that should not run on a scheduler thread.

    static DexFuture *
    run_blocking_thumbnailer (gpointer user_data)
    {
      ThumbnailJob *job = user_data;

      return thumbnail_job_run_blocking (job);
    }

    DexLimiter *limiter = dex_limiter_new (4);
    DexThreadPool *pool = dex_thread_pool_new (4);
    DexFuture *future = dex_limiter_run_on_pool (limiter,
                                                 pool,
                                                 run_blocking_thumbnailer,
                                                 thumbnail_job_ref (job),
                                                 thumbnail_job_unref);

The returned future resolves or rejects with the result of the submitted thread-pool work. Once the work has been submitted, dropping the returned future does not stop the work; it is allowed to complete so the permit can be released.

For stackless async workflows, use `dex_limiter_run_coroutine()`. This acquires a permit and then runs a `DexCoroutineFunc` from a scheduler.

    static DexFuture *
    load_one_cache (DexCoroutineContext *context,
                    gpointer             user_data)
    {
      DEX_COROUTINE_BEGIN (context);

      DEX_COROUTINE_END ();
    }

    DexLimiter *limiter = dex_limiter_new (4);
    DexFuture *future = dex_limiter_run_coroutine (limiter,
                                                   NULL,
                                                   load_one_cache,
                                                   g_object_ref (key),
                                                   g_object_unref);

`load_one_cache` uses stackless coroutine style and avoids allocating a fiber stack while still participating in limiter scoping.

## Manual Scopes

Use `dex_limiter_acquire()` and `dex_limiter_release()` when a permit must cover a custom scope that is not a single fiber.

    g_autoptr(GError) error = NULL;

    if (dex_await (dex_limiter_acquire (limiter), &error))
      {
        do_limited_work ();
        dex_limiter_release (limiter);
      }

Release exactly once for every successful acquisition. Prefer `dex_limiter_run()` when possible because it handles release on both success and failure paths.

## Choosing a Limit

Choose a limit that matches the constrained resource, not the number of items in the queue.

- Use a small value for remote services, databases, and APIs with rate limits.
- Use a value near the number of useful worker threads for CPU-heavy work.
- Use a larger value for I/O-heavy local work if the underlying storage or service benefits from parallelism.
- Keep separate limiters for unrelated resources so one workload does not consume another workload’s budget.

## Shutdown

Call `dex_limiter_close()` when no new work should start. Pending and future acquisitions reject with `DEX_ERROR_SEMAPHORE_CLOSED`. Work that already holds a permit may continue, but releasing after close does not make new permits available.

If you need to wait for graceful shutdown, call `dex_limiter_close_after_drain()`, which both closes the limiter and returns a future that resolves once all queued acquires have settled and all running holders have released.