# `Portals`
[🔗](https://github.com/Ziinc/portals/blob/v0.1.0/lib/portals.ex#L1)

Public API for invoking functions in a supervised external-language
worker (FR-2).

Two entry points exist:

  * `start_worker/1` (Phase 3) — one directly managed `Portals.Connection`.
    The functions below (`call/5`, `async/5`, `await/2`, `cancel/2`,
    `health/1`) operate on it.
  * `start_pool/1` (Phase 4) — a supervised, load-balanced
    `Portals.Pool` of many workers with overflow and checkout. Use
    `Portals.Pool.call/5` etc. (same shapes as the functions below) once
    you have a pool.

## Example

    {:ok, conn} =
      Portals.start_worker(command: "python3", args: ["priv/python_worker.py"])

    {:ok, "hello"} = Portals.call(conn, "bench_worker", "echo", ["hello"])

    {:ok, pool} =
      Portals.start_pool(command: "python3", args: ["priv/python_worker.py"], size: 4)

    {:ok, "hello"} = Portals.Pool.call(pool, "bench_worker", "echo", ["hello"])

# `async`

```elixir
@spec async(GenServer.server(), binary(), binary(), list(), keyword()) ::
  {:ok, Portals.Request.t()} | {:error, Portals.Error.t()}
```

Invoke `module`/`function` with `args` without blocking; await the result with `await/2`.

# `await`

```elixir
@spec await(Portals.Request.t(), timeout()) ::
  {:ok, term()} | {:error, Portals.Error.t()}
```

Block for the result of a request started with `async/5`.

# `call`

```elixir
@spec call(GenServer.server(), binary(), binary(), list(), keyword()) ::
  {:ok, term()} | {:error, Portals.Error.t()}
```

Invoke `module`/`function` with `args` and block for the result.

# `call!`

```elixir
@spec call!(GenServer.server(), binary(), binary(), list(), keyword()) :: term()
```

Like `call/5`, but returns the value directly and raises `Portals.CallError` on failure.

# `cancel`

```elixir
@spec cancel(Portals.Request.t()) :: :ok
```

Cooperatively cancel a request started with `async/5`. Idempotent.

# `health`

```elixir
@spec health(GenServer.server()) :: map()
```

A snapshot of one worker's status and in-flight request count.

# `open_stream`

```elixir
@spec open_stream(GenServer.server(), binary(), binary(), list(), keyword()) ::
  {:ok, Portals.Stream.t()} | {:error, Portals.Error.t()}
```

Open a bidirectional stream against `module`/`function` (FR-5).

Returns a `%Portals.Stream{}` handle; see `Portals.Stream` for the
explicit send/half-close/receive API and the `Enumerable` API.

    {:ok, stream} = Portals.open_stream(conn, "bench_worker", "echo_stream", [])
    :ok = Portals.Stream.send_enumerable(stream, ["a", "b"])
    ["a", "b"] = stream |> Portals.Stream.to_enumerable() |> Enum.to_list()

# `start_pool`

```elixir
@spec start_pool(Portals.Pool.start_opts()) :: {:ok, pid()} | {:error, term()}
```

Start a supervised pool of workers. See `Portals.Pool.start_link/1` for options.

# `start_worker`

```elixir
@spec start_worker(Portals.Connection.start_opts()) :: {:ok, pid()} | {:error, term()}
```

Start and hand-shake with one worker. See `Portals.Connection.start_link/1` for options.

# `stop_worker`

```elixir
@spec stop_worker(GenServer.server(), timeout()) :: :ok
```

Stop a worker, sending `SHUTDOWN` and waiting up to `deadline_ms` before force-killing it.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
