Skip to content

Lifetimes And Events

Chris Michael edited this page Oct 6, 2026 · 1 revision

Pudu

Lifetimes And Events

The factory reuses a name's pipeline — its handlers and its pooled connections — until the handler lifetime runs out (two minutes unless set with withHandlerLifetime), then builds the next. A client made from an expired pipeline keeps working; the expired pipeline is closed after one more lifetime.

Programs hear about this, and about every request, through plain listeners passed to Factory.buildWith. The package carries the events through a mediator of its own; programs never build or import one.

module Ex07Lifetimes

import Std.Concurrent as Concurrent
import Std.Http as Http
import Std.Io as Io
import Std.Result as Result
import Std.Sync as Sync
import PuduLangHttpClient.Client as Client
import PuduLangHttpClient.Events as Events
import PuduLangHttpClient.Factory as Factory
import PuduLangHttpClient.Factory.Builder as Builder
import PuduLangHttpClient.Stub as Stub

export fn main() -> Int {
  let lines = Sync.cell([])
  let lock = Sync.mutex()
  let note = fn(line: Str) -> () {
    let _noted = Sync.withLock(&lock, fn() -> () { let _kept = Sync.set(&lines, Result.unwrapOr(Sync.get(&lines), []).push(line)) })
  }
  let listeners = Events.listeners()
    .onPipelineChanged(|event: Events.PipelineChanged| note(event.client + " #" + show(event.generation) + " " + show(event.stage)))
    .onRequestCompleted(|event: Events.RequestCompleted| note(event.method + " " + event.uri + " " + show(event.status)))
  let stub = Stub.create()
  Stub.respond(&stub, Http.Get, "/ping", 200, "pong")
  let factory = match Factory.buildWith(&Factory.Options{listeners: listeners}, [
      Builder.named("pinger").withBaseAddress("https://ping.test/").withPrimary(|| Stub.send(&stub)).withHandlerLifetime(20)
    ]) {
    case Ok(built) => built
    case Err(invalid) => panic(Factory.explain(&invalid))
  }
  let _first = Client.getText(&Factory.createClient(&factory, "pinger"), "ping")
  let _rested = Concurrent.sleep(30)
  let _second = Client.getText(&Factory.createClient(&factory, "pinger"), "ping")
  let _graced = Concurrent.sleep(30)
  let _third = Client.getText(&Factory.createClient(&factory, "pinger"), "ping")
  Factory.dispose(&factory)
  let seen = Result.unwrapOr(Sync.get(&lines), [])
  for line in seen { let _said = Io.writeLine(line) }
  if seen.contains("pinger #1 Closed") && seen.contains("pinger #3 Created") && seen.contains("GET https://ping.test/ping 200") { 0 } else { 1 }
}

Check it and run it:

pudu check src/Ex07Lifetimes.pudu
pudu run src/Ex07Lifetimes.pudu

Output:

pinger #1 Created
GET https://ping.test/ping 200
pinger #2 Created
pinger #1 Expired
GET https://ping.test/ping 200
pinger #3 Created
pinger #2 Expired
pinger #1 Closed
GET https://ping.test/ping 200
pinger #3 Closed
pinger #2 Closed

Events.listeners() with onRequestStarted, onRequestCompleted, onRequestFailed, and onPipelineChanged adds listeners; every event derives Json.Encode, so a listener can forward it as JSON. With withHandlerLifetime(Defaults.INFINITE) a pipeline is never renewed — pair it with withPooledConnectionLifetime so connections are.

Related

Clone this wiki locally