Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Listening

A listener streams notifications to your code as they arrive. In C++ you start one with client.watch, passing a WatchRequest that says what to listen to and a handler that says what to do with each notification. The library calls your handler on a background thread until the listener ends, whether because you stopped it or because it failed.

The handler

Subclass aviso::NotificationHandler and override two methods. on_notification runs once per notification and returns whether to keep going. on_end runs once, after the last notification, and tells you whether the listener stopped because you asked or because it failed.

class Handler : public aviso::NotificationHandler {
 public:
  bool on_notification(const aviso::Notification& n) override {
    std::cout << n.event_type() << " #" << n.sequence() << " "
              << n.identifier_json() << " " << n.payload_json() << '\n';
    return true;  // keep going; return false to stop
  }
  void on_end(const std::optional<aviso::ErrorInfo>& error) override {
    if (error) {
      std::cerr << "watch failed: " << error->message << '\n';
    }
  }
};

Do not leave on_end empty. It is the only place a failed watch is reported. An exception thrown out of on_notification also ends up there: it stops the watch, and on_end receives an AvisoErrorKind_Internal error whose message names the exception. wait() returns normally whether the watch ended because your handler said so or because the connection was refused, so a handler that ignores on_end turns every failure into a quiet exit. The examples keep a small base class in common.hpp that stores the error, and a finish() helper that turns it into the exit code.

The callbacks run on a runtime thread, so the handler must be thread-safe. They must not make blocking aviso calls: a blocking call from inside a callback throws an aviso::Error of kind AvisoErrorKind_InvalidUsage, and the async verbs are the way to make requests from there (see Async). The Notification you receive is a borrowed view, valid only for that call. Its accessors return owned std::strings, so copy out anything you want to keep.

Starting and stopping

Build a WatchRequest, then call client.watch. It returns a Watch whose destructor stops the listener and waits for it, so you cannot leave one running by accident. The handler you pass must outlive the Watch. client.watch consumes the request: calling a setter on it afterwards, or passing it to another watch, throws an aviso::Error of kind AvisoErrorKind_InvalidUsage. Build a new request for each watch.

Handler handler;
aviso::WatchRequest request("test_event");
aviso::Watch watch = client.watch(request, handler);

// Block until listening ends: the handler returned false, the stream ended,
// or it failed. Or just let `watch` go out of scope to stop and wait.
watch.wait();

watch.stop() asks for a graceful stop. It returns at once and is safe from any thread, including from inside the handler. After a stop(), on_end runs with no error, so a stopped watch looks the same as one whose handler returned false. watch.wait() blocks until on_end has returned, so do not call it from inside a callback.

A signal handler may not call stop() itself, since almost nothing is allowed inside one. The pattern that works is to set a flag in the signal handler and have a small thread call stop() when the flag turns.

examples/cpp/basics/03_listen.cpp is this in full: it prints each notification and stops itself after three. examples/cpp/resilience/04_stop_from_outside.cpp runs until Ctrl+C or a timer, and stops the way a real listener would.

Several watches at once

To listen to several things through one handler, give each WatchRequest a name in a WatchSet and call client.watch_many. The handler derives from aviso::MultiNotificationHandler. It works like the single-watch handler, and on_notification also receives the name of the watch:

class Printer : public aviso::MultiNotificationHandler {
 public:
  bool on_notification(const std::string& name,
                       const aviso::Notification& n) override {
    std::cout << name << ": " << n.identifier_json() << '\n';
    return true;
  }
  void on_end(const std::optional<aviso::ErrorInfo>& error) override {
    if (error) {
      std::cerr << "listening failed: " << error->message << '\n';
    }
  }
};

aviso::WatchRequest operational("mars");
operational.filter_json(R"({"class": "od"})");
aviso::WatchRequest research("mars");
research.filter_json(R"({"class": "rd", "step": 0})");

aviso::WatchSet watches;
watches.add("operational", operational).add("research", research);

Printer printer;
aviso::Watch watch = client.watch_many(watches, printer);
watch.wait();

Each line shows which watch delivered the notification:

operational: {"class":"od","step":"6"}
research: {"class":"rd","step":"0"}
operational: {"class":"od","step":"12"}
  • Each request keeps its own event type, filter, start position and triggers.
  • The watches are read in turn, so a busy watch cannot starve a quiet one.
  • The callbacks never run at the same time, so the handler needs no lock for its own state.
  • The returned Watch works as for a single watch: stop(), wait() and the destructor act on every watch in the set.
  • watch_many consumes the WatchSet, and add consumes each WatchRequest. Using either again throws an aviso::Error of kind AvisoErrorKind_InvalidUsage.

When one watch fails

By default, one failing watch stops them all, and on_end receives its error. The message begins with the name of the watch:

listening failed: watch 'research': http 400: ... Unknown field 'stepp' ...

To drop only the failing watch and keep the others, override on_error, which also receives the name, and return true:

bool on_error(const std::string& name,
              const aviso::ErrorInfo& error) override {
  std::cerr << name << " dropped: " << error.message << '\n';
  return true;
}

on_end still receives an error if every watch fails, so the program cannot end quietly with nothing running. An exception thrown from on_notification or on_error stops every watch, and on_end receives an AvisoErrorKind_Internal error whose message names the exception.

examples/cpp/basics/07_watch_many.cpp is a complete program: it runs two watches and stops once each has delivered three notifications.

Resuming where you left off

Every notification carries a sequence number. If your process stops, the last sequence you handled is all you need to carry on without a gap: ask for everything after it, then stay live. The server replays what you missed first and switches to live delivery on the same connection.

aviso::WatchRequest request("test_event");
request.watch_from_sequence(last_handled);  // replay after this, then live

Where you keep last_handled is up to you. A file is enough for a single process; record it in on_notification after handling, not before, so a crash mid-handler replays that notification rather than skipping it. That gives you at-least-once delivery, which is what you want for anything that matters.

watch_from_date does the same with a timestamp instead of a sequence.

examples/cpp/resilience/01_resume_from_sequence.cpp keeps the position in a file. Run it, stop it, publish a few notifications, run it again: the missed ones arrive first.

Reading history only

Sometimes you want a batch, not a feed. replay_from_sequence and replay_from_date deliver what the server has kept and then end the watch by themselves, without waiting for anything new. The handler can return true throughout; wait() still returns when history runs out.

aviso::WatchRequest request("test_event");
request.replay_from_date("2026-01-01T00:00:00Z");  // history, then end

A server caps how much one replay may return. When you hit the cap the watch ends with an error of kind AvisoErrorKind_HistoryGap whose message names the limit, and the notifications you did get are complete and in order up to the last one. Ask again with replay_from_sequence from that last sequence to get the rest, and repeat until the watch ends without an error.

examples/cpp/resilience/02_replay_only.cpp does exactly that: it reads everything since a date in as many batches as the cap requires, then exits with a count.

Stopping at an end point

replay_until_sequence and replay_until_date end the replay at an end point instead of at the last stored notification. A sequence is the last one delivered, inclusive; a date ends with the last notification published at or before that time. Combine either with a replay_from_* setter, in any order:

aviso::WatchRequest request("test_event");
request.replay_from_date("2026-01-01T00:00:00Z")
    .replay_until_date("2026-01-02T00:00:00Z");  // one day, then end

The start is exclusive and the end inclusive for sequences: replay_from_sequence(10) with replay_until_sequence(20) delivers the matching notifications with sequences 11 to 20. An end point in the future ends at the last stored notification. If the connection drops, the watch resumes up to the same end point.

An end point without a replay_from_* setter makes on_end report AvisoErrorKind_InvalidInput when the watch starts, since a live watch has no end. A sequence end that is not after a sequence start is reported as AvisoErrorKind_Config. An end point needs aviso-server 0.13.0 or later; an older server rejects the request with an AvisoErrorKind_Http error, status 400.

Filtering

filter_json narrows the stream to notifications whose identifier matches a JSON object: an exact value per key, or a server-defined rule object.

aviso::WatchRequest("test_event").filter_json(R"({"date":"20260101"})");

Fields you leave out match anything. The filter is applied by the server, so notifications that do not match never reach your process. examples/cpp/basics/05_filter.cpp listens for one date and lets everything else go past.

Numeric and enum constraints

With the schema and seeds from the weather tutorial, use this request with the Handler above and a client built as in A first call, using your test server’s address and credentials. Replay delivers B and C, then ends; inspect their labels in n.payload_json().

aviso::WatchRequest request("weather");
request.replay_from_sequence(0).filter_json(R"({
  "date": "20260913",
  "severity": {"gte": 5},
  "anomaly": {"between": [40, 50]},
  "region": {"in": ["north", "south"]}
})");
Handler handler;
auto watch = client.watch(request, handler);
watch.wait();

For live delivery, omit replay_from_sequence(0) and start before publishing. The raw string contains JSON objects with numeric operands, not quoted JSON objects. See Filters for supported operators and schema requirements.