diff --git a/src/fw/services/hrm/hrm_manager.c b/src/fw/services/hrm/hrm_manager.c index 4e83a2fea..aa5504839 100644 --- a/src/fw/services/hrm/hrm_manager.c +++ b/src/fw/services/hrm/hrm_manager.c @@ -129,6 +129,11 @@ T_STATIC uint32_t prv_num_system_task_events_queued(void) { &s_manager_state.system_task_event_buffer); return avail_bytes / sizeof(PebbleHRMEvent); } + +// Used by unit tests +T_STATIC uint32_t prv_get_dropped_events_count(void) { + return s_manager_state.dropped_events; +} #endif static void prv_handle_accel_data(void * data) { @@ -506,7 +511,10 @@ void hrm_manager_new_data_cb(const HRMData *data) { kernel_bg_features_sent |= feature; } prv_populate_hrm_event(&hrm_event, feature, data); - PBL_ASSERTN(prv_event_put(state, &hrm_event)); + if (!prv_event_put(state, &hrm_event)) { + // Consumer queue full (e.g. app not draining events); drop instead of panicking. + ++s_manager_state.dropped_events; + } } // If this is an app subscription, see if we need to send an "expiring" event. We check @@ -516,8 +524,12 @@ void hrm_manager_new_data_cb(const HRMData *data) { .event_type = HRMEvent_SubscriptionExpiring, .expiring.session_ref = state->session_ref, }; - PBL_ASSERTN(prv_event_put(state, &hrm_event)); - state->sent_expiration_event = true; + if (prv_event_put(state, &hrm_event)) { + state->sent_expiration_event = true; + } else { + // Retry on the next sample rather than panicking. + ++s_manager_state.dropped_events; + } } if (state->expire_utc && (utc_now >= state->expire_utc)) { diff --git a/tests/fw/services/test_hrm_manager.c b/tests/fw/services/test_hrm_manager.c index f70e4d542..fbca95803 100644 --- a/tests/fw/services/test_hrm_manager.c +++ b/tests/fw/services/test_hrm_manager.c @@ -43,6 +43,7 @@ extern uint32_t prv_num_system_task_events_queued(void); extern TimerID prv_get_timer_id(void); extern bool prv_can_turn_sensor_on(void); extern void prv_charger_event_cb(PebbleEvent *e); +extern uint32_t prv_get_dropped_events_count(void); // ----------------------------------------------------------------------------- @@ -63,10 +64,14 @@ bool hrm_is_enabled(HRMDevice *dev) { return s_hrm_state.enabled; } static const QueueHandle_t FAKE_APP_QUEUE = (QueueHandle_t) 1337; static uint32_t s_event_count; +static bool s_queue_full; static PebbleEvent s_events_received[16]; signed portBASE_TYPE xQueueGenericSend(QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, portBASE_TYPE xCopyPosition) { cl_assert_equal_i((intptr_t) xQueue, (intptr_t) FAKE_APP_QUEUE); + if (s_queue_full) { + return pdFALSE; + } if (s_event_count < ARRAY_LENGTH(s_events_received)) { s_events_received[s_event_count] = *((PebbleEvent *)pvItemToQueue); } @@ -153,6 +158,7 @@ void test_hrm_manager__initialize(void) { s_activity_prefs_heart_rate_is_enabled = true; s_event_count = 0; + s_queue_full = false; s_num_cb_events_1 = 0; s_num_cb_events_2 = 0; memset(&s_hrm_state, 0, sizeof(s_hrm_state)); @@ -746,6 +752,34 @@ void test_hrm_manager__can_turn_sensor_on(void) { cl_assert(hrm_is_enabled(HRM)); } +// A full app event queue (app not draining events fast enough) must not panic the firmware. +// The HRM sample is dropped and counted, and delivery resumes once the queue drains. +void test_hrm_manager__app_queue_full_drops_without_panic(void) { + stub_pebble_tasks_set_current(PebbleTask_App); + + AppInstallId app_id = 1; + const uint16_t expire_s = SECONDS_PER_MINUTE; + HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); + + // App is not draining its event queue. + s_queue_full = true; + + // Must not panic; the event is dropped (not delivered) and counted. + prv_fake_send_new_data(); + cl_assert_equal_i(s_event_count, 0); + cl_assert_equal_i(prv_get_dropped_events_count(), 1); + + // Subscription survives and delivery resumes once the queue drains. + s_queue_full = false; + prv_fake_send_new_data(); + cl_assert_equal_i(s_event_count, 1); + cl_assert_equal_i(s_events_received[0].type, PEBBLE_HRM_EVENT); + cl_assert_equal_i(s_events_received[0].hrm.event_type, HRMEvent_BPM); + cl_assert_equal_i(prv_get_dropped_events_count(), 1); + + sys_hrm_manager_unsubscribe(session_ref); +} + // Test that OffWrist quality is delivered immediately without delay void test_hrm_manager__immediate_off_wrist(void) { stub_pebble_tasks_set_current(PebbleTask_KernelBackground); diff --git a/tests/wscript b/tests/wscript index 28e78c8cf..cf1e1334c 100644 --- a/tests/wscript +++ b/tests/wscript @@ -331,7 +331,6 @@ def build(bld): 'test_graphics_fill_circle_8bit.c', 'test_graphics_gtransform_1bit.c', 'test_graphics_gtransform_8bit.c', - 'test_hrm_manager.c', 'test_kickstart.c', 'test_launcher_menu_layer.c', 'test_pfs.c',