simplernble 1.2.1-dev132 → 1.2.1-dev136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/native/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1-dev132
1
+ 1.2.1-dev136
@@ -0,0 +1,166 @@
1
+ #pragma once
2
+
3
+ #include <simpleble/Logging.h>
4
+
5
+ #include <condition_variable>
6
+ #include <exception>
7
+ #include <functional>
8
+ #include <memory>
9
+ #include <mutex>
10
+ #include <queue>
11
+ #include <string>
12
+ #include <thread>
13
+ #include <utility>
14
+
15
+ #include "Common.hpp"
16
+ #include "VM.hpp"
17
+
18
+ namespace SimpleJNI {
19
+
20
+ /**
21
+ * Runs Java callbacks one at a time, in order, on a single thread attached to the JVM.
22
+ *
23
+ * Follows kvn::thread_runner: stop() runs the queued functions, functions enqueued afterwards are dropped, and the
24
+ * runner may be destroyed from one of its own functions. On top of that, it does what a JNI callback thread needs:
25
+ * - The thread starts on the first enqueue, since the JavaVM is only known after JNI_OnLoad.
26
+ * - The thread stays attached under the runner's name, which debuggers and ANR traces show, and detaches before it
27
+ * ends, as ART requires. It attaches as a daemon, so it does not keep a desktop JVM from exiting.
28
+ * - A function that throws, or that leaves a Java exception pending, is logged under the runner's name and the thread
29
+ * carries on.
30
+ */
31
+ class ThreadRunner final {
32
+ public:
33
+ explicit ThreadRunner(std::string name) : _state(std::make_shared<state>()) { _state->name = std::move(name); }
34
+
35
+ // Remove copy constructor and copy assignment
36
+ ThreadRunner(const ThreadRunner&) = delete;
37
+ ThreadRunner& operator=(const ThreadRunner&) = delete;
38
+
39
+ ~ThreadRunner() {
40
+ if (!on_own_thread()) {
41
+ stop();
42
+ return;
43
+ }
44
+
45
+ // Destroyed from one of its own functions: the thread cannot join itself. It keeps its own reference to the
46
+ // state, and detaches from the JVM and ends once the current function returns.
47
+ std::queue<std::function<void()>> dropped;
48
+ {
49
+ std::lock_guard<std::mutex> lock(_state->mutex);
50
+ _state->stop = true;
51
+ dropped.swap(_state->queue);
52
+ }
53
+ _state->cv.notify_one();
54
+ _thread.detach();
55
+ }
56
+
57
+ /**
58
+ * Functions enqueued after stop() are dropped.
59
+ */
60
+ void enqueue(std::function<void()> func) {
61
+ {
62
+ std::lock_guard<std::mutex> lock(_state->mutex);
63
+ if (_state->stop) return;
64
+ if (!_thread.joinable()) {
65
+ _thread = std::thread(&ThreadRunner::thread_func, _state);
66
+ _thread_id = _thread.get_id();
67
+ }
68
+ _state->queue.push(std::move(func));
69
+ }
70
+ _state->cv.notify_one();
71
+ }
72
+
73
+ /**
74
+ * Run the functions already enqueued, then end the thread.
75
+ *
76
+ * Safe to call more than once. When called from one of the runner's own functions, it returns right away and the
77
+ * thread ends once the queue is empty.
78
+ */
79
+ void stop() {
80
+ {
81
+ std::lock_guard<std::mutex> lock(_state->mutex);
82
+ _state->stop = true;
83
+ }
84
+ _state->cv.notify_one();
85
+
86
+ // Once stopped, enqueue() no longer starts the thread, so it is safe to read here.
87
+ if (on_own_thread()) return;
88
+
89
+ std::lock_guard<std::mutex> lock(_join_mutex);
90
+ if (_thread.joinable()) {
91
+ _thread.join();
92
+ }
93
+ }
94
+
95
+ private:
96
+ /** Shared with the thread, so the thread never touches the runner itself. */
97
+ struct state {
98
+ std::mutex mutex;
99
+ std::condition_variable cv;
100
+ std::queue<std::function<void()>> queue;
101
+ bool stop = false;
102
+ std::string name;
103
+ };
104
+
105
+ static void log_error(const state& shared, const std::string& message) noexcept {
106
+ SimpleBLE::Logging::Logger::get()->log(SimpleBLE::Logging::Level::Error, shared.name, __FILE__, __LINE__,
107
+ __func__, message);
108
+ }
109
+
110
+ static void thread_func(std::shared_ptr<state> shared) noexcept {
111
+ try {
112
+ VM::attach_as_daemon(shared->name.c_str());
113
+ } catch (const std::exception& exception) {
114
+ log_error(*shared, std::string("Failed to attach callback thread: ") + exception.what());
115
+
116
+ // Nothing can run without the JVM, so drop the queued functions instead of letting the queue grow.
117
+ std::queue<std::function<void()>> dropped;
118
+ std::lock_guard<std::mutex> lock(shared->mutex);
119
+ shared->stop = true;
120
+ dropped.swap(shared->queue);
121
+ return;
122
+ }
123
+
124
+ while (true) {
125
+ std::function<void()> func;
126
+ {
127
+ std::unique_lock<std::mutex> lock(shared->mutex);
128
+ shared->cv.wait(lock, [&shared] { return shared->stop || !shared->queue.empty(); });
129
+ if (shared->stop && shared->queue.empty()) {
130
+ break;
131
+ }
132
+ func = std::move(shared->queue.front());
133
+ shared->queue.pop();
134
+ }
135
+ run(*shared, func);
136
+ }
137
+
138
+ VM::detach();
139
+ }
140
+
141
+ static void run(const state& shared, const std::function<void()>& func) noexcept {
142
+ try {
143
+ func();
144
+ } catch (const std::exception& exception) {
145
+ log_error(shared, std::string("Exception in callback thread: ") + exception.what());
146
+ } catch (...) {
147
+ log_error(shared, "Unknown exception in callback thread");
148
+ }
149
+
150
+ // A pending Java exception would break every later JNI call on this thread.
151
+ try {
152
+ Exception::check(VM::env());
153
+ } catch (const std::exception& exception) {
154
+ log_error(shared, std::string("Java exception left pending in callback thread: ") + exception.what());
155
+ }
156
+ }
157
+
158
+ bool on_own_thread() const { return std::this_thread::get_id() == _thread_id; }
159
+
160
+ std::shared_ptr<state> _state;
161
+ std::thread _thread;
162
+ std::thread::id _thread_id;
163
+ std::mutex _join_mutex;
164
+ };
165
+
166
+ } // namespace SimpleJNI
@@ -87,6 +87,25 @@ class VM {
87
87
  }
88
88
  }
89
89
 
90
+ // Attaches the calling thread as a daemon, unless it is attached already, so a desktop JVM can exit while the
91
+ // thread is still running.
92
+ static void attach_as_daemon(const char* thread_name = nullptr) {
93
+ JavaVM* jvm = VM::jvm();
94
+ JNIEnv* env = nullptr;
95
+ if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) == JNI_OK) return;
96
+
97
+ // OpenJDK declares the name as char*, but the JVM only reads it.
98
+ JavaVMAttachArgs args{JNI_VERSION_1_6, const_cast<char*>(thread_name), nullptr};
99
+ #ifdef __ANDROID__
100
+ auto result = jvm->AttachCurrentThreadAsDaemon(&env, &args);
101
+ #else
102
+ auto result = jvm->AttachCurrentThreadAsDaemon(reinterpret_cast<void**>(&env), &args);
103
+ #endif
104
+ if (result != JNI_OK) {
105
+ throw std::runtime_error("Failed to attach the current thread to the JVM");
106
+ }
107
+ }
108
+
90
109
  static void detach() {
91
110
  JavaVM* jvm = VM::jvm();
92
111
  jvm->DetachCurrentThread();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simplernble",
3
- "version": "1.2.1-dev132",
3
+ "version": "1.2.1-dev136",
4
4
  "description": "SimpleBLE for React Native: a cross-platform Bluetooth Low Energy library for iOS and Android",
5
5
  "keywords": [
6
6
  "react-native",
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>libsimpleble.a</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64</string>
11
+ <string>ios-arm64_x86_64-simulator</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>libsimpleble.a</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
+ <string>x86_64</string>
17
18
  </array>
18
19
  <key>SupportedPlatform</key>
19
20
  <string>ios</string>
21
+ <key>SupportedPlatformVariant</key>
22
+ <string>simulator</string>
20
23
  </dict>
21
24
  <dict>
22
25
  <key>BinaryPath</key>
23
26
  <string>libsimpleble.a</string>
24
27
  <key>LibraryIdentifier</key>
25
- <string>ios-arm64_x86_64-simulator</string>
28
+ <string>ios-arm64</string>
26
29
  <key>LibraryPath</key>
27
30
  <string>libsimpleble.a</string>
28
31
  <key>SupportedArchitectures</key>
29
32
  <array>
30
33
  <string>arm64</string>
31
- <string>x86_64</string>
32
34
  </array>
33
35
  <key>SupportedPlatform</key>
34
36
  <string>ios</string>
35
- <key>SupportedPlatformVariant</key>
36
- <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>