---
source_url: https://www.pubnub.com/docs/sdks/c/faq
title: FAQ for C SDK
updated_at: 2026-09-24T12:25:41.000Z
sdk_name: PubNub C SDK
sdk_version: 1.0.0
---

# FAQ for C SDK

PubNub C SDK, use the latest version: 1.0.0

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

## 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](https://www.pubnub.com/docs/sdks/c/api-reference/app-context.md#custom-metadata) and [Publish](https://www.pubnub.com/docs/sdks/c/api-reference/publish-and-subscribe.md#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](https://www.pubnub.com/docs/sdks/c/api-reference/configuration.md#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](https://www.pubnub.com/docs/sdks/c/api-reference/publish-and-subscribe.md#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](https://www.pubnub.com/docs/sdks/c/api-reference/misc.md#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](https://www.pubnub.com/docs/sdks/c/troubleshooting.md) 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](https://www.pubnub.com/docs/sdks/c/api-reference/misc.md#futures-and-async-lifecycle) for the full function list, and [Environment Setup — Calling patterns](https://www.pubnub.com/docs/sdks/c/environment-setup.md#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:

```c
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](https://www.pubnub.com/docs/sdks/c/api-reference/misc.md#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](https://www.pubnub.com/docs/sdks/c/migration-guides/migrating-from-c-core-v7.md). 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_OK` → `PUBNUB_OK`). The legacy SDK's synchronous-by-default calls and its UUID generators also have no direct counterpart; the migration guide covers both.

Last updated at: 2026-09-24T12:25:41.000Z
