Skip to content

SDK development guide fixes - #169

Merged
arifBurakDemiray merged 1 commit into
stagingfrom
guide-fixes
Nov 10, 2026
Merged

arifBurakDemiray merged 1 commit into
stagingfrom
guide-fixes

Conversation

@turtledreams

Copy link
Copy Markdown
Contributor

What this does

Brings the C++ SDK in line with the updated SDK Development Guide. It fixes the crash and data-loss problems a review against the guide found (each reproduced before the fix), corrects behaviour that differs from the guide, and adds the small missing pieces the guide requires. It also makes the cpp17 and cpp20 CI jobs compile at the standard they name.

The XX.XX.XX section of CHANGELOG.md lists every change.

Breaking changes (the "Minor breaking change" lines)

  1. Every request is POST. A custom HTTP client gets usePost true for every request and must support POST; alwaysUsePost is deprecated and has no effect.
  2. start() refuses a server URL without http:// or https:// or without a host, an empty app key and YOUR_APP_KEY: it logs an error and the SDK stays off.
  3. Recorded events are sent on the session update interval (60 s by default) or at the event queue threshold, not every 3 s. flushEvents() sends them right away.
  4. beginSession, updateSession and endSession work only with enableManualSessionControl(). A manual updateSession() always reports the duration.
  5. Remote config downloads use method=rc and no longer enroll the user in A/B tests (enrollABOnRCDownload() turns it back on). Automatic downloads run at the end of start(), after a device ID change and when consent is given.
  6. Config values below their minimum are ignored with a warning instead of being raised.
  7. setLocation drops values that don't fit their form.
  8. SQLite builds keep the device ID between starts, and a stored device ID wins at start(): a different device ID given with setDeviceID before start() is ignored with a warning. enableClearStoredDeviceID() before start() uses the given one instead.

The consent ("cr") breaking line in the same section comes from #168.

Crashes and data loss fixed (each reproduced first)

  • start() without a device ID threw. The SDK now generates a UUID (type 1), and SQLite builds store it.
  • views() or crash() before start() dereferenced null. They are usable now; calls are ignored with a warning.
  • A log listener that throws broke SDK calls or ended the update thread. Its exceptions are contained.
  • An SBS segmentation whitelist or blacklist stripped [CLY]_view segmentation, so views were missing from the dashboard. Internal events are no longer filtered.
  • With SBS st=false, setUserDetails and a device ID change blocked for 15 s and then deleted the waiting events, and a running session never ended.
  • Any 2xx answer, such as a captive portal page, counted as delivered and deleted the request. Success now needs a 2xx JSON object with result.
  • A negative event count was sent as 18446744073709551615, and an empty event key was sent. Counts below 1 become 1 (w750); an empty key is ignored (w701).
  • An empty device ID or app key was accepted, every request was rejected, and the queue stayed blocked.
  • stop() lost end_session and the waiting events. They are now queued before the update thread stops.
  • start() without its fourth argument never updated sessions and held events until 100 were waiting. The update loop always runs now.
  • Crash reports without _os and _app_version were dropped by the server. They are filled from the metrics; a crash without a stack trace is ignored.
  • Limits of 0 emptied keys and values or kept a single request. They are ignored with w121.

Other guide alignment

  • Requests: t, av and rr on every request, failed request cool-down (setFailedRequestCooldown, 60 s), drop by age (setRequestDropAgeHours, SBS dort), backoff (disableBackoffMechanism, SBS bom*), gradual queue trimming (disableGradualRequestCleaner).
  • Events: event IDs (id, cvid, pvid, peid), validation with log codes, filters and limits applied to custom events and unknown [CLY]_ keys, timed events recorded at their end, event queue capped at 1000.
  • Views: segment, first view per session, reserved keys protected, closeViewWithName closes the last started view, stopAllViews().
  • SBS: missing keys fall back to the developer value or default, empty filter lists are ignored, provided settings are accepted as {v,t,c} or the inner object and are not persisted, no fetch at all when updates are disabled, fetch after a device ID change (with merge: after the merge request is delivered).
  • Remote config: values are cleared on a device ID change, a fetch requested while one runs follows it.
  • Crashes: breadcrumbs cleared per crash, setCrashFilterCallback with _ob, setCustomCrashSegmentation, crash().clearBreadcrumbs(), _cvid.
  • Location: validation, no IP in the separate location request, values set before start() applied, disableLocation(), an erase at the end of init when location is off.
  • Metrics: automatic _os and _os_version; setMetrics values still win.

Device ID at start

Follows the guide's "Device ID State Management During Init": a stored device ID wins; otherwise the given one is used; otherwise a UUID is generated. With enableClearStoredDeviceID(), events the stored user left in the database are first queued under the stored device ID, then the given or a new device ID is used and stored. Called after start(), it is ignored with a warning.

CI: C++ standard

Every target pinned CXX_STANDARD 14, which overrides -DCMAKE_CXX_STANDARD, so the cpp17 and cpp20 jobs compiled at C++14. Now CMAKE_CXX_STANDARD defaults to 14 only when it is unset, and target_compile_features(... PRIVATE cxx_std_14) keeps C++14 as the minimum: a parent project on C++11 still builds the SDK as C++14, and consumers get no new requirement. Checked flags: unset gives c++14, 11 gives c++14, 17 gives c++17, 20 gives c++20 (CMake 3.16 and 3.29 with GCC; MSVC stdcpp14 and stdcpp20). Conan builds now use the profile's cppstd.

The first real C++17 and C++20 runs crashed in the test suite: tests/mutex_exception_safety.cpp shared a plain static pointer between the test thread and its log callback, which the always-running update loop calls from its own thread. The pointer is now atomic and read once per call.

Decisions for review

  1. A manual updateSession() always sends the duration, also sooner than the update interval.
  2. endSession() while SBS turned session tracking off and no session runs returns false with a debug log, without w760.
  3. The server URL scheme is case-insensitive and lower-cased.
  4. setSDKBehaviorSettings accepts both the full server answer ({v,t,c}) and its inner c object.
  5. Automatic remote config downloads run at the end of start(), after a device ID change and when consent is given, not at session start.
  6. A stored event that is not valid JSON is dropped with a warning (memory queue and SQLite) instead of throwing out of the call that sends events.

Tests

  • 8 new test files with 56 test cases: init, request_pipeline, event_recording, session_lifecycle, behavior_settings, remote_config, crash_reporting, location. Each fix's test failed on the old code first.
  • Existing tests changed only where the guide changes the expected behaviour: POST, the t parameter, the cool-down, method=rc, session calls in automatic mode, events queued before a session ends, provided SBS not stored, the view segment key.

Verification (local)

On the PR head:

Build Result
MinGW GCC 10.3, SQLite, C++14 173/173
MinGW GCC 10.3, SQLite, C++20 173/173
Linux GCC 9.4, SQLite + curl, C++17 and C++20 (as the CI jobs) 173/173 each
MSVC 2019, C++20 compiles without warnings
Linux ASAN, init and mutex_exception_safety tests 11/11, no reports

Earlier in the work, before the last test-only change (the race fix above):

Build Result
MSVC 2019 Release (as the CI Windows cells), SQLite / memory queue, sample 173/173 / 153/153, sample builds
Linux GCC 9.4, SQLite + curl, C++14 173/173

Earlier still, before the device ID rule and the CMake change: UBSAN 173/173, static custom-HTTP build 172/172, full ASAN 172/173 (the failing test asserted an asynchronous fetch's position; fixed, then passed 5 times in a row under ASAN).

Not run: a real Countly server, macOS, clang, the system nlohmann_json flavour (WSL has 3.7.3, the SDK needs 3.11 or newer), TSAN, the Qt demo. clang-format was not run (not installed).

Left for later

Remote config callbacks, cache and A/B APIs, offline mode, user profile modifiers and save(), view pause and resume, manual feedback widgets, queue operations, custom headers and certificate pinning. The help-center C++ docs need the breaking changes and the new APIs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wc7z4S3SpREqhYT28iCtkY

Brings the C++ SDK in line with the updated SDK development guide:
crash and data-loss fixes, wrong behaviours and small missing features.

Crashes and data loss: start() without a device ID no longer throws (a
UUID is generated, SQLite builds store it); views() and crash() before
start() are safe; a throwing log listener is contained; SBS segmentation
filters no longer strip the SDK's own view data; st=false no longer
blocks for 15 s and deletes waiting events; only a 2xx JSON answer with
"result" counts as delivered; invalid event counts, empty keys, device
IDs and app keys are refused; stop() sends waiting events and ends the
session; crash reports carry the OS and app version; limits below their
minimum are ignored.

Behaviour: every request is POST; start() refuses invalid server URLs
and app keys; events are sent on the session update timer; session calls
work only with manual session control; remote config uses method=rc and
enrolls in A/B tests only on request; a stored device ID wins at start()
unless enableClearStoredDeviceID() is called; SBS, views, crashes and
location follow the guide.

Added: getDeviceID, getDeviceIDType, enableClearStoredDeviceID, t/av/rr
on every request, OS metrics, event IDs, failed request cool-down,
request drop age, backoff, gradual queue trimming, crash filter and
crash segmentation, clearBreadcrumbs, stopAllViews, disableLocation,
enrollABOnRCDownload.

Build: CMAKE_CXX_STANDARD is honoured, with C++14 as the default and
the minimum, so the cpp17 and cpp20 CI jobs compile at C++17 and C++20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wc7z4S3SpREqhYT28iCtkY
@arifBurakDemiray
arifBurakDemiray merged commit cdda3b2 into staging Nov 10, 2026
43 of 44 checks passed
@arifBurakDemiray
arifBurakDemiray deleted the guide-fixes branch November 10, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants