On this page

FAQ for C SDK

I built a pubnub_json_value_t* tree for a request option. Do I free it, or does the SDK?

It depends on which field you filled in. The rule is not the same everywhere.

For publish.h's message_value and meta_value, signal.h's message_value, and presence.h's state_value, the SDK only borrows the tree. It serializes the tree synchronously before the call returns, and you must destroy it yourself afterward with pubnub_json_destroy().

For app_context.h's custom_value, ownership transfers to the SDK. It consumes and frees the tree during the call, on both success and failure, and you must never touch or free it again.

Applying the publish rule to custom_value causes a double free. Applying the App Context rule to message_value or state_value causes a leak. Neither mistake produces a compile error, a runtime error, or a status code. Treat this as a rule to get right up front, not one you can debug your way out of later.

For the full per-field breakdown, see Custom metadata and Publish.

If I set a config string in pubnub_config_t, whose memory is it?

It depends on which lifecycle function you call, not on the field. pubnub_init() borrows every const char* field, such as subscribe_key, publish_key, and user_id, so you must keep that memory valid for the entire life of the context. pubnub_create() deep-copies the same fields, so you can free or reuse the original buffers as soon as the call returns. The six provider pointer fields (allocator, transport, serialization, platform, crypto_module, logger) are always borrowed, on both paths, with no exception. Mixing these two rules up, by assuming a deep copy under pubnub_init() or assuming a borrow under pubnub_create(), is the single most common configuration mistake in this SDK.

See Initialization for the per-field ownership table.

Can I call publish or subscribe directly on a pubnub_entity_t?

No. pubnub_channel(), pubnub_channel_group(), pubnub_channel_metadata(), and pubnub_user_metadata() return a pubnub_entity_t, a thin, opaque handle exposing only pubnub_entity_name() and pubnub_entity_type(). It carries no operations of its own. Every publish, signal, subscribe, and App Context function takes a context plus plain string identifiers, never an entity handle. An entity handle exists only to construct a subscription with pubnub_subscription_create(), and you can destroy it immediately afterward without keeping it alive for the subscription's lifetime. If you are coming from another PubNub SDK where some operations bind directly to an entity, do not carry that model over to this SDK.

See Entities for details.

What happens if I don't call pubnub_future_release()?

The request-pool slot behind that future is never returned to the pool. On a hosted build (full/minimal profile, stdlib allocator) the slot's memory leaks. On an embedded build with a fixed slot count (PUBNUB_CFG_MAX_IN_FLIGHT_REQUESTS), each unreleased future permanently occupies one slot, and the pool eventually drains: new calls start failing with PUBNUB_ERR_QUEUE_FULL with no other symptom pointing at the real cause.

Call pubnub_future_release(fut) exactly once for every future you receive, on every code path, including an error path, a cancellation path, and a future that turned out to be PUBNUB_FUTURE_INVALID. See Utility Methods — Futures and async lifecycle for the full lifecycle contract.

My call returned PUBNUB_ERR_QUEUE_FULL. What should I do?

The context has reached its request-pool limit: PUBNUB_CFG_MAX_IN_FLIGHT_REQUESTS in-flight requests, plus PUBNUB_CFG_MAX_PENDING_REQUESTS more waiting behind them. PUBNUB_ERR_QUEUE_FULL is returned only once both are exhausted. On embedded targets this is intentional backpressure, not a bug. Wait for an outstanding future to complete, release it, then retry.

The most common root cause is not actually running out of legitimate concurrent requests, but forgetting pubnub_future_release() on completed ones (see the previous entry) — check that first. If you genuinely need more concurrency, you can raise PUBNUB_CFG_MAX_IN_FLIGHT_REQUESTS/PUBNUB_CFG_MAX_PENDING_REQUESTS at compile time, but on the arena allocator (the embedded profile's default) each additional in-flight slot adds PUBNUB_CFG_RESPONSE_BUFFER_SIZE + PUBNUB_CFG_OBJECT_BUFFER_SIZE + PUBNUB_CFG_SCRATCH_BUFFER_SIZE bytes to the arena's memory budget — see Troubleshooting — Arena pool exhausts after a few requests for the full sizing math. On full/minimal (stdlib allocator), raising the limits mainly costs concurrent heap, not a fixed budget.

When should I use cooperative polling vs. pubnub_await vs. pubnub_async?

  • Cooperative polling (pubnub_process(ctx) in a loop until pubnub_future_is_ready(fut)): no background thread, no OS synchronization primitives required. The right choice for bare-metal, single-task RTOS, and existing event-loop integrations.
  • Blocking pubnub_await(fut): blocks the calling thread until the future completes. On a threaded build it polls internally with a yield between checks; on a cooperative build it drives pubnub_process() for you, so you don't need your own loop either way. The simplest pattern when blocking the calling thread is acceptable.
  • Callback-driven pubnub_async(fut, callback, user_data): registers a callback that fires exactly once when the future completes (immediately, inline, if it's already done). Release the future from inside the callback. On a build without a background thread, the callback still only fires when something calls pubnub_process()pubnub_async() doesn't start driving I/O by itself.

All three read the same result accessors and require the same single pubnub_future_release() call. See Utility Methods — Futures and async lifecycle for the full function list, and Environment Setup — Calling patterns for the setup each style needs.

How do I allocate the right amount of memory for pubnub_init()?

Call pubnub_context_size(), declared in pubnub/client.h, to get the exact byte count for the current build:

size_t             sz  = pubnub_context_size();
pubnub_context_t* ctx = (pubnub_context_t*)malloc(sz);
pubnub_res_t rc = pubnub_init(ctx, &cfg);

The size can vary between build configurations (which features are compiled in, which platform provider is linked), so always call the function at runtime rather than hardcoding a byte count. See Utility Methods — Context size for the static-buffer variant of this pattern, with the required alignment macro.

How do I migrate from the legacy C-Core SDK?

The name changes are extensive enough that they have their own page: Migrate from C-Core v7. Start there for the full mapping. Two changes come up often enough to call out here: pubnub_alloc()/pubnub_free() became pubnub_create()/pubnub_destroy() (or pubnub_init()/pubnub_deinit() for caller-provided memory), and every PNR_* result code became a PUBNUB_* one (PNR_OKPUBNUB_OK). The legacy SDK's synchronous-by-default calls and its UUID generators also have no direct counterpart; the migration guide covers both.