Skip to content

Commit 77c86f9

Browse files
committed
Destroy ZMQ context before program exits
1 parent fc92b73 commit 77c86f9

2 files changed

Lines changed: 82 additions & 25 deletions

File tree

Source/Plugins/EventBroadcaster/EventBroadcaster.cpp

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,60 @@
1111
#include "EventBroadcaster.h"
1212
#include "EventBroadcasterEditor.h"
1313

14-
std::shared_ptr<void> EventBroadcaster::getZMQContext() {
15-
// Note: C++11 guarantees that initialization of static local variables occurs exactly once, even
16-
// if multiple threads attempt to initialize the same static local variable concurrently.
14+
EventBroadcaster::ZMQContext* EventBroadcaster::sharedContext = nullptr;
15+
CriticalSection EventBroadcaster::sharedContextLock{};
16+
17+
EventBroadcaster::ZMQContext::ZMQContext(const ScopedLock& lock)
1718
#ifdef ZEROMQ
18-
static const std::shared_ptr<void> ctx(zmq_ctx_new(), zmq_ctx_destroy);
19-
#else
20-
static const std::shared_ptr<void> ctx;
19+
: context(zmq_ctx_new())
20+
#endif
21+
{
22+
sharedContext = this;
23+
}
24+
25+
// ZMQContext is a ReferenceCountedObject with a pointer in each instance's
26+
// socket pointer, so this only happens when the last instance is destroyed.
27+
EventBroadcaster::ZMQContext::~ZMQContext()
28+
{
29+
ScopedLock lock(sharedContextLock);
30+
sharedContext = nullptr;
31+
#ifdef ZEROMQ
32+
zmq_ctx_destroy(context);
33+
#endif
34+
}
35+
36+
void* EventBroadcaster::ZMQContext::createZMQSocket()
37+
{
38+
#ifdef ZEROMQ
39+
jassert(context != nullptr);
40+
return zmq_socket(context, ZMQ_PUB);
2141
#endif
22-
return ctx;
42+
}
43+
44+
EventBroadcaster::ZMQSocketPtr::ZMQSocketPtr()
45+
: std::unique_ptr<void, decltype(&closeZMQSocket)>(nullptr, &closeZMQSocket)
46+
{
47+
ScopedLock lock(sharedContextLock);
48+
if (sharedContext == nullptr)
49+
{
50+
// first one, create the context
51+
context = new ZMQContext(lock);
52+
}
53+
else
54+
{
55+
// use already-created context
56+
context = sharedContext;
57+
}
58+
59+
#ifdef ZEROMQ
60+
reset(context->createZMQSocket());
61+
#endif
62+
}
63+
64+
EventBroadcaster::ZMQSocketPtr::~ZMQSocketPtr()
65+
{
66+
// close the socket before the context might get destroyed.
67+
reset(nullptr);
2368
}
2469

2570
int EventBroadcaster::unbindZMQSocket()
@@ -70,8 +115,6 @@ void EventBroadcaster::reportActualListeningPort(int port)
70115

71116
EventBroadcaster::EventBroadcaster()
72117
: GenericProcessor ("Event Broadcaster")
73-
, zmqContext (getZMQContext())
74-
, zmqSocket (nullptr)
75118
, listeningPort (0)
76119
{
77120
setProcessorType (PROCESSOR_TYPE_SINK);
@@ -105,7 +148,7 @@ int EventBroadcaster::setListeningPort(int port, bool forceRestart)
105148
#ifdef ZEROMQ
106149
// unbind current socket (if any) to free up port
107150
unbindZMQSocket();
108-
zmqSocketPtr newSocket(zmq_socket(zmqContext.get(), ZMQ_PUB));
151+
ZMQSocketPtr newSocket;
109152
auto editor = static_cast<EventBroadcasterEditor*>(getEditor());
110153
int status = 0;
111154

Source/Plugins/EventBroadcaster/EventBroadcaster.h

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
#include <memory>
2626

27-
2827
class EventBroadcaster : public GenericProcessor
2928
{
3029
public:
@@ -45,27 +44,42 @@ class EventBroadcaster : public GenericProcessor
4544

4645

4746
private:
48-
void sendEvent(const MidiMessage& event, float eventSampleRate) const;
49-
static std::shared_ptr<void> getZMQContext();
47+
class ZMQContext : public ReferenceCountedObject
48+
{
49+
public:
50+
ZMQContext(const ScopedLock& lock);
51+
~ZMQContext() override;
52+
void* createZMQSocket();
53+
private:
54+
void* context;
55+
};
56+
57+
static void closeZMQSocket(void* socket);
58+
59+
class ZMQSocketPtr : public std::unique_ptr<void, decltype(&closeZMQSocket)>
60+
{
61+
public:
62+
ZMQSocketPtr();
63+
~ZMQSocketPtr();
64+
private:
65+
ReferenceCountedObjectPtr<ZMQContext> context;
66+
};
67+
5068
int unbindZMQSocket();
5169
int rebindZMQSocket();
52-
static void closeZMQSocket (void* socket);
70+
71+
void sendEvent(const MidiMessage& event, float eventSampleRate) const;
5372
static String getEndpoint(int port);
5473
// called from getListeningPort() depending on success/failure of ZMQ operations
5574
void reportActualListeningPort(int port);
5675

57-
// encapuslate closing sockets when their pointers go out of scope
58-
struct zmqSocketPtr : public std::unique_ptr<void, decltype(&closeZMQSocket)>
59-
{
60-
zmqSocketPtr(void* ptr)
61-
: std::unique_ptr<void, decltype(&closeZMQSocket)>(ptr, &closeZMQSocket)
62-
{}
63-
};
64-
65-
const std::shared_ptr<void> zmqContext;
66-
zmqSocketPtr zmqSocket;
76+
// share a "dumb" pointer that doesn't take part in reference counting.
77+
// want the context to be terminated by the time the static members are
78+
// destroyed (see: https://github.com/zeromq/libzmq/issues/1708)
79+
static ZMQContext* sharedContext;
80+
static CriticalSection sharedContextLock;
81+
ZMQSocketPtr zmqSocket;
6782
int listeningPort;
68-
6983
};
7084

7185

0 commit comments

Comments
 (0)