Publish-Subscribe System (psub)
XSTAR's Publish-Subscribe mechanism enables loosely coupled communication between modules. Publishers send messages on a topic, and callbacks subscribed to that topic are automatically invoked. Publishers and subscribers are unaware of each other.
Architecture
Publisher Subscriber
psub_publish("net/status", data) → cb1(data, sdat1)
→ cb2(data, sdat2)
→ ... (all callbacks subscribed to this topic)
A global context is created at system startup via pure_initcall, providing psub_* global convenience interfaces. The underlying ps_* implementation supports creating independent context instances.
Two-layer Interface
Global Interface (psub)
Uses the system-wide global publish-subscribe context, simple and direct, suitable for most scenarios.
void psub_publish(const char * topic, void * pdat);
void psub_subscribe(const char * topic, void (*cb)(void *, void *), void * sdat, int oneshot);
void psub_unsubscribe(const char * topic, void (*cb)(void *, void *), void * sdat);
Instance Interface (ps)
Can create independent publish-subscribe contexts, suitable for scenarios requiring isolation (e.g., different subsystems managing their own).
struct psctx_t * ps_alloc(int size);
void ps_free(struct psctx_t * ctx);
void ps_publish(struct psctx_t * ctx, const char * topic, void * pdat);
void ps_subscribe(struct psctx_t * ctx, const char * topic, void (*cb)(void *, void *), void * sdat, int oneshot);
void ps_unsubscribe(struct psctx_t * ctx, const char * topic, void (*cb)(void *, void *), void * sdat);
For the instance interface, ps_subscribe uses (cb, sdat) as the unique identifier, so the same callback may be subscribed to the same topic multiple times with different sdat values. ps_unsubscribe matches by (cb, sdat) exactly to remove the corresponding subscription. The global psub_* interface follows the same semantics.
API Details
Publish
void psub_publish(const char * topic, void * pdat);
Publish a message to topic, pdat is a pointer to data passed by the publisher. All callbacks subscribed to this topic are invoked in subscription order. If the topic has no subscribers, no operation is performed.
Subscribe
void psub_subscribe(const char * topic, void (*cb)(void *, void *), void * sdat, int oneshot);
Subscribe to topic, cb is the callback function, sdat is the subscriber's private data. A subscription is uniquely identified by the (cb, sdat) pair, so the same cb can be subscribed multiple times to the same topic with different sdat values; duplicate subscriptions of the same (cb, sdat) pair are ignored.
oneshot = 0— Persistent subscription, triggered on every publishoneshot = 1— One-time subscription, automatically cancelled after being triggered once
Callback signature: void cb(void * pdat, void * sdat)
pdat— Data passed by the publishersdat— Private data passed at subscription time
Unsubscribe
void psub_unsubscribe(const char * topic, void (*cb)(void *, void *), void * sdat);
Cancel subscriptions on topic. Two matching modes are supported:
cb != NULL— Exact match by(cb, sdat); only the specified subscription is removed. Thesdatargument must match the value used at subscription time, otherwise the entry is not removed.cb == NULL— Wildcard match; removes all subscriptions of the giventopic(thesdatargument is ignored in this case).
The topic itself is not deleted; it remains in the context for future subscriptions and is only reclaimed in bulk at ps_free.
Automatic Cleanup
- One-time subscriptions are automatically removed after being triggered
- Context hash table automatically expands on demand
- Topics created on subscription are retained until
ps_free(they are not individually reclaimed when their subscriber list becomes empty)
Usage Examples
Basic publish-subscribe
static void on_network_status(void * pdat, void * sdat)
{
int * status = (int *)pdat;
printf("network status: %d\n", *status);
}
psub_subscribe("net/status", on_network_status, NULL, 0);
int status = 1;
psub_publish("net/status", &status);
/* on_network_status is called */
With private data
struct my_app_t {
int id;
};
static void on_data(void * pdat, void * sdat)
{
struct my_app_t * app = (struct my_app_t *)sdat;
char * data = (char *)pdat;
printf("app%d received: %s\n", app->id, data);
}
struct my_app_t app = { .id = 42 };
psub_subscribe("data/update", on_data, &app, 0);
One-time subscription
static void on_init_done(void * pdat, void * sdat)
{
printf("initialization complete\n");
}
psub_subscribe("system/init", on_init_done, NULL, 1);
psub_publish("system/init", NULL); /* Triggered and automatically cancelled */
psub_publish("system/init", NULL); /* No longer triggered */
Independent context
struct psctx_t * my_ps = ps_alloc(16);
ps_subscribe(my_ps, "event/a", my_callback, NULL, 0);
ps_publish(my_ps, "event/a", data);
ps_free(my_ps);
Bulk unsubscribe
psub_subscribe("net/status", cb1, &ctx1, 0);
psub_subscribe("net/status", cb2, &ctx2, 0);
psub_subscribe("net/status", cb3, NULL, 0);
/* Remove a single subscription by exact match */
psub_unsubscribe("net/status", cb1, &ctx1);
/* Wildcard: remove all remaining subscriptions on this topic */
psub_unsubscribe("net/status", NULL, NULL);