Skip to content

Commit 1864b46

Browse files
authored
Merge pull request #1017 from SignalK/fix/894-registry-cleanup
fix(core): clean up static registries on destruction (#894)
2 parents 967fca1 + 77aa942 commit 1864b46

18 files changed

Lines changed: 412 additions & 13 deletions

src/sensesp/signalk/signalk_emitter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "signalk_emitter.h"
22

3+
#include <algorithm>
4+
35
namespace sensesp {
46

57
std::vector<SKEmitter*> SKEmitter::sources_;
@@ -8,6 +10,13 @@ SKEmitter::SKEmitter(const String& sk_path) : sk_path_{sk_path} {
810
sources_.push_back(this);
911
}
1012

13+
SKEmitter::~SKEmitter() {
14+
sources_.erase(std::remove(sources_.begin(), sources_.end(), this),
15+
sources_.end());
16+
}
17+
18+
void SKEmitter::clear_registry() { sources_.clear(); }
19+
1120
void SKEmitter::add_metadata(JsonArray& meta) {
1221
SKMetadata* my_meta = this->get_metadata();
1322
if (my_meta != NULL) {

src/sensesp/signalk/signalk_emitter.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class SKEmitter : virtual public Observable {
2626
*/
2727
SKEmitter(const String& sk_path);
2828

29+
/**
30+
* Unregisters this emitter from the static source registry so the
31+
* registry never holds a dangling pointer.
32+
*/
33+
virtual ~SKEmitter();
34+
2935
/**
3036
* Returns the data to be reported to the server as
3137
* a Signal K json object.
@@ -65,6 +71,12 @@ class SKEmitter : virtual public Observable {
6571

6672
static const std::vector<SKEmitter*>& get_sources() { return sources_; }
6773

74+
/**
75+
* Empties the source registry. Intended for clean app restart and test
76+
* isolation; not for normal runtime use.
77+
*/
78+
static void clear_registry();
79+
6880
protected:
6981
String sk_path_{};
7082

src/sensesp/signalk/signalk_listener.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "signalk_listener.h"
22

3+
#include <algorithm>
4+
35
#include "sensesp/system/saveable.h"
46

57
namespace sensesp {
@@ -19,6 +21,19 @@ SKListener::SKListener(const String &sk_path, int listen_delay,
1921
this->load();
2022
}
2123

24+
SKListener::~SKListener() {
25+
take_semaphore();
26+
listeners_.erase(std::remove(listeners_.begin(), listeners_.end(), this),
27+
listeners_.end());
28+
release_semaphore();
29+
}
30+
31+
void SKListener::clear_registry() {
32+
take_semaphore();
33+
listeners_.clear();
34+
release_semaphore();
35+
}
36+
2237
bool SKListener::take_semaphore(uint64_t timeout_ms) {
2338
if (timeout_ms == 0) {
2439
return xSemaphoreTakeRecursive(semaphore_, portMAX_DELAY) == pdTRUE;

src/sensesp/signalk/signalk_listener.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ class SKListener : virtual public Observable, public FileSystemSaveable {
3535
SKListener(const String& sk_path, int listen_delay,
3636
const String& config_path = "");
3737

38+
/**
39+
* Unregisters this listener from the static registry so the registry never
40+
* holds a dangling pointer. Takes the listener semaphore because the WS
41+
* client task iterates the registry under the same lock.
42+
*/
43+
virtual ~SKListener();
44+
3845
/**
3946
* Returns the current Signal K path. An empty string
4047
* is returned if this particular source is not configured
@@ -66,6 +73,12 @@ class SKListener : virtual public Observable, public FileSystemSaveable {
6673

6774
static const std::vector<SKListener*>& get_listeners() { return listeners_; }
6875

76+
/**
77+
* Empties the listener registry. Intended for clean app restart and test
78+
* isolation; not for normal runtime use. Takes the listener semaphore.
79+
*/
80+
static void clear_registry();
81+
6982
static bool take_semaphore(uint64_t timeout_ms = 0);
7083
static void release_semaphore();
7184

src/sensesp/signalk/signalk_put_request_listener.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#include "signalk_put_request_listener.h"
22

3+
#include <algorithm>
4+
5+
#include "signalk_listener.h"
6+
37
namespace sensesp {
48

59
std::vector<SKPutListener*> SKPutListener::listeners_;
@@ -8,4 +12,19 @@ SKPutListener::SKPutListener(const String& sk_path) : sk_path{sk_path} {
812
listeners_.push_back(this);
913
}
1014

15+
SKPutListener::~SKPutListener() {
16+
// The WS client iterates this registry while holding the SKListener
17+
// semaphore, so mutate it under the same lock.
18+
SKListener::take_semaphore();
19+
listeners_.erase(std::remove(listeners_.begin(), listeners_.end(), this),
20+
listeners_.end());
21+
SKListener::release_semaphore();
22+
}
23+
24+
void SKPutListener::clear_registry() {
25+
SKListener::take_semaphore();
26+
listeners_.clear();
27+
SKListener::release_semaphore();
28+
}
29+
1130
} // namespace sensesp

src/sensesp/signalk/signalk_put_request_listener.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ class SKPutListener : virtual public Observable {
2626
*/
2727
SKPutListener(const String& sk_path);
2828

29+
/**
30+
* Unregisters this listener from the static registry so the registry never
31+
* holds a dangling pointer. Takes the SKListener semaphore because the WS
32+
* client task iterates this registry under that same lock.
33+
*/
34+
virtual ~SKPutListener();
35+
2936
String& get_sk_path() { return sk_path; }
3037

3138
virtual void parse_value(const JsonObject& put) = 0;
@@ -34,6 +41,12 @@ class SKPutListener : virtual public Observable {
3441
return listeners_;
3542
}
3643

44+
/**
45+
* Empties the listener registry. Intended for clean app restart and test
46+
* isolation; not for normal runtime use. Takes the SKListener semaphore.
47+
*/
48+
static void clear_registry();
49+
3750
protected:
3851
String sk_path{};
3952

src/sensesp/transforms/transform.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ TransformBase::TransformBase(const String& config_path)
1717
transforms_.insert(this);
1818
}
1919

20+
TransformBase::~TransformBase() { transforms_.erase(this); }
21+
22+
void TransformBase::clear_registry() { transforms_.clear(); }
23+
2024
} // namespace sensesp

src/sensesp/transforms/transform.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,28 @@ class TransformBase : public FileSystemSaveable {
3131
public:
3232
TransformBase(const String& config_path);
3333

34-
// Primary purpose of this was to supply Signal K sources
35-
// (now handled by SKEmitter::get_sources). Should
36-
// this be deprecated?
34+
/**
35+
* Unregisters this transform from the static registry so the registry
36+
* never holds a dangling pointer.
37+
*/
38+
virtual ~TransformBase();
39+
40+
// The transform registry's original purpose (supplying Signal K sources) is
41+
// now handled by SKEmitter::get_sources(); nothing in the framework consumes
42+
// get_transforms() anymore. get_sources() is not a drop-in replacement -- it
43+
// returns only the Signal K emitters, not the full transform set, for which
44+
// there is no equivalent accessor.
45+
[[deprecated("Deprecated and unused internally; no direct replacement")]]
3746
static const std::set<TransformBase*>& get_transforms() {
3847
return transforms_;
3948
}
4049

50+
/**
51+
* Empties the transform registry. Intended for clean app restart and test
52+
* isolation; not for normal runtime use.
53+
*/
54+
static void clear_registry();
55+
4156
private:
4257
static std::set<TransformBase*> transforms_;
4358
};

src/sensesp/ui/config_item.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace sensesp {
44

55
std::map<String, std::shared_ptr<ConfigItemBase>> ConfigItemBase::config_items_;
66

7+
void ConfigItemBase::clear_registry() { config_items_.clear(); }
8+
79
template <>
810
const char* get_schema_type_string(const int /*dummy*/) {
911
return "number";

src/sensesp/ui/config_item.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ std::shared_ptr<ConfigItemT<T>> ConfigItem(std::shared_ptr<T>);
7070
class ConfigItemBase
7171
: virtual public std::enable_shared_from_this<ConfigItemBase> {
7272
public:
73+
virtual ~ConfigItemBase() = default;
74+
7375
const String& get_title() const { return title_; }
7476
ConfigItemBase* set_title(const String& title) {
7577
title_ = title;
@@ -161,6 +163,20 @@ class ConfigItemBase
161163
return sorted_config_items;
162164
}
163165

166+
/**
167+
* @brief Empty the config item registry.
168+
*
169+
* Releases the registry's shared_ptr references. Intended for clean app
170+
* restart and test isolation; not for normal runtime use.
171+
*
172+
* Unlike the raw-pointer registries (SKEmitter, Transform, StatusPageItem),
173+
* this registry owns its entries via shared_ptr, so there is no
174+
* unregister-on-destruction: clearing the map releases the objects. A
175+
* self-erasing destructor here would be re-entrant and is deliberately
176+
* absent.
177+
*/
178+
static void clear_registry();
179+
164180
virtual bool load() = 0;
165181
virtual bool refresh() = 0;
166182
virtual bool save() = 0;

0 commit comments

Comments
 (0)