Files
Eric Macdonald e7e987ec2c SM: fix table races, SQLite busy, and use-after-free on deprovision
Stress testing of repeated service provision and deprovision exposed
three latent concurrency issues in SM. Each can affect stability
when service-management activity overlaps with audit cycles or
external CLI access to the SM database. The fixes are independent
but share a common theme: SM's shared tables and database
connections needed proper serialization and retry handling.

Bug 1 - service / dependency tables had no mutex
------------------------------------------------
The in-memory service and service-dependency lists were accessed
by both the FSM thread and the API dispatch thread without
synchronization. This is fixed by adding a recursive mutex to
each table, with internal *_unlocked variants for code paths that
already hold the lock. Lock order is service -> dependency.
Recursive mutexes are required because the engine and dependency
callbacks legitimately re-enter the service table from inside a
dependency walk.

Bug 2 - SQLite connections had no busy handler
----------------------------------------------
Database connections opened without a busy handler returned
SQLITE_BUSY immediately on writer-writer contention. This is
fixed by registering sqlite3_busy_handler in sm_db_connect for
every SM connection, with a bounded retry budget and throttled
logging.

Bug 3 - process-failure deregister used a freed pointer
-------------------------------------------------------
sm_deprovision_service called the process-failure deregister
after sm_service_deprovision had already freed the service
struct. The deregister is reordered to run before the free so
the pointer is still valid. Deregister remains best-effort.

Files
-----
service-mgmt/sm-db/src/sm_db.c                    - busy handler
service-mgmt/sm/src/sm_service_table.c            - mutex + locking
service-mgmt/sm/src/sm_service_dependency_table.c - mutex + locking
service-mgmt/sm/src/sm_configure.cpp              - reorder dereg

Note: The provisioning / deprovisioning of SM services in the following
      test plan was restricted to ceph services where on

      AIO: services include
        storage-services           :ceph-osd
        storage-services           :mgr-restful-plugin
        storage-services           :storage-networking
        controller-services        :ceph-mon
        controller-services        :drbd-cephmon
        storage-monitoring-services:ceph-manager

      Standard Storage System: services include only
        storage-services           :mgr-restful-plugin
        storage-services           :storage-networking
        storage-monitoring-services:ceph-manager

Note: Some tests were verified with additional logging added on top of
      this update.

Test Plan
---------

PASS: Verify install AIO DX and 2+2+2 Storage system (6 times  3 each)
PASS: Repeated provision / deprovision soak across multiple ceph
      services completes cleanly with SM stable across the run.
PASS: Perform the above test as a 1000 loop overnight soak in both
      All-In-One and 2+2+2 Standard Storage System while verifying
      in each case:
      - no SM traps: sm-trap.log remains empty and nore sm coredumps
      - no memory leak over soak
      - no file descriptor leaks
      - no unexpected logging
PASS: Verify swact back and forth following above reprovisioning soak
PASS: Verify new mutex lock handling confirming the locking is
      exercised without deadlock. Verified with debug logging.
PASS: Inject sm_service_dependency_table_load concurrent with a
      dependency walk; load blocks until the walk completes.
PASS: Verify the Busy retry handling is exercized and works properly
      during the above reprovisioning soaks.
      - debug log: "sm_db.c(58):
         Database 'Busy': retry 1/200 (1000 us/retry) at 27016.528
      Above log was hit twice in the Standard storage system soak.
      Above log was hit 12 times, ~45 (30-60) mins apart in AIO soak.
PASS: Hold the database write lock long enough to force the
      give-up branch. Tested by FIT during a reprovisioning cycle
      The following 4 debug logs
      show the max retry handling

      - sm_db.c(58):
          Database 'Busy': retry 200/200 (1000 us/retry) at 65910.756
      - sm_db.c(51): Database has been 'Busy' for 200 msecs :
          gave up after 200 retries (1000 us/retry, 200 ms max)
      - sm_db_nodes.c(409): Failed to update, sql=UPDATE NODES SET
          READY_STATE = 'disabled' WHERE NAME = 'controller-0';,
          rc=5, error=database is locked.
      - debug Shutting down.

      The busy condition is detected, SM exits as before but now only
      after 200 retries 1 msec retries.

PASS: Verify no log corruption with Bug3 fix.
      Observed debug log showing
      "Process Failure Deregistration BEFORE Freeing the service"
PASS: AIO-DX swact loop, 50 swacts per side.
PASS: After above reprovisioning soaks Lock/Unlock compute, controller,
      and storage hosts; Verify proper handling ; no SM-side regression.

Closes-Bug: 2154600
Assisted-By: Amazon Q (Auto)
Change-Id: I8ebeae48d00ba50fd4952ef54ca0d4d1f4019b7d
Signed-off-by: Eric Macdonald <eric.macdonald@windriver.com>
2026-07-09 13:22:09 -04:00
..
2018-05-31 07:36:26 -07:00