|
1 | 1 | #include "EventLoop.h" |
2 | 2 |
|
| 3 | +#include <android/api-level.h> |
3 | 4 | #include <sys/eventfd.h> |
4 | 5 | #include <sys/timerfd.h> |
5 | 6 | #include <unistd.h> |
@@ -46,6 +47,7 @@ void RunGuarded(F&& body) { |
46 | 47 |
|
47 | 48 | namespace tns { |
48 | 49 |
|
| 50 | +bool EventLoop::claimGateRegistered_ = false; |
49 | 51 | jclass EventLoop::EVENT_LOOP_HANDLER_CLASS = nullptr; |
50 | 52 | jmethodID EventLoop::EVENT_LOOP_HANDLER_CTOR = nullptr; |
51 | 53 | jmethodID EventLoop::EVENT_LOOP_HANDLER_POST = nullptr; |
@@ -106,14 +108,23 @@ void EventLoop::BindToCurrentThread() { |
106 | 108 | EVENT_LOOP_HANDLER_CANCEL_IDENTIFIED = env.GetMethodID( |
107 | 109 | EVENT_LOOP_HANDLER_CLASS, "cancelIdentified", "(Ljava/lang/Object;)Z"); |
108 | 110 | EVENT_LOOP_HANDLER_RELEASE = env.GetMethodID(EVENT_LOOP_HANDLER_CLASS, "release", "()V"); |
109 | | - // the @CriticalNative gate must be bound explicitly (name resolution |
110 | | - // doesn't apply to the critical calling convention on older ART) |
111 | | - static const JNINativeMethod claimMethod = { |
| 111 | + // The @CriticalNative gate must be bound explicitly (name resolution |
| 112 | + // doesn't apply to the critical calling convention on older ART). |
| 113 | + // Below API 26 ART ignores the annotation and calls through the |
| 114 | + // normal JNI ABI, so bind the standard-convention twin there. |
| 115 | + const bool criticalAbi = android_get_device_api_level() >= 26; |
| 116 | + const JNINativeMethod claimMethod = { |
112 | 117 | const_cast<char*>("nativeClaimToken"), const_cast<char*>("(JJ)Z"), |
113 | | - reinterpret_cast<void*>(EventLoop::ClaimTokenCritical)}; |
| 118 | + criticalAbi ? reinterpret_cast<void*>(EventLoop::ClaimTokenCritical) |
| 119 | + : reinterpret_cast<void*>(EventLoop::ClaimTokenLegacy)}; |
114 | 120 | JNIEnv* rawEnv = env; |
115 | | - jint registered = rawEnv->RegisterNatives(EVENT_LOOP_HANDLER_CLASS, &claimMethod, 1); |
116 | | - assert(registered == 0); |
| 121 | + if (rawEnv->RegisterNatives(EVENT_LOOP_HANDLER_CLASS, &claimMethod, 1) == 0) { |
| 122 | + claimGateRegistered_ = true; |
| 123 | + } else { |
| 124 | + rawEnv->ExceptionClear(); |
| 125 | + DEBUG_WRITE_FORCE( |
| 126 | + "EventLoop: claim gate registration failed; timer tokens stay plain"); |
| 127 | + } |
117 | 128 | } |
118 | 129 | JniLocalRef handler(env.NewObject(EVENT_LOOP_HANDLER_CLASS, EVENT_LOOP_HANDLER_CTOR, |
119 | 130 | reinterpret_cast<jlong>(this))); |
@@ -283,16 +294,28 @@ uint64_t EventLoop::PostTimerToken(jlong uptimeMillis, int timerId) { |
283 | 294 | } |
284 | 295 | uint64_t word = 0; |
285 | 296 | auto& cell = claimCells_[((uint32_t) timerId) & (kClaimCells - 1)]; |
286 | | - uint64_t expected = 0; |
287 | | - uint64_t candidate = (((uint64_t) (uint32_t) timerId) << 2) | kCellActive; |
288 | | - if (cell.compare_exchange_strong(expected, candidate, std::memory_order_acq_rel)) { |
289 | | - word = candidate; |
| 297 | + if (claimGateRegistered_) { |
| 298 | + uint64_t expected = 0; |
| 299 | + uint64_t candidate = (((uint64_t) (uint32_t) timerId) << 2) | kCellActive; |
| 300 | + if (cell.compare_exchange_strong(expected, candidate, std::memory_order_acq_rel)) { |
| 301 | + word = candidate; |
| 302 | + } |
| 303 | + // a busy slot (previous token of the same interval still in flight, |
| 304 | + // or an id collision) downgrades this token to plain; clear then uses |
| 305 | + // tombstones |
290 | 306 | } |
291 | | - // a busy slot (previous token of the same interval still in flight, or an |
292 | | - // id collision) downgrades this token to plain; clear then uses tombstones |
293 | 307 | JEnv env; |
294 | | - env.CallVoidMethod(handler_, EVENT_LOOP_HANDLER_POST_TOKEN, uptimeMillis, |
295 | | - (jint) (word >> 32), (jint) (word & 0xffffffffull)); |
| 308 | + try { |
| 309 | + env.CallVoidMethod(handler_, EVENT_LOOP_HANDLER_POST_TOKEN, uptimeMillis, |
| 310 | + (jint) (word >> 32), (jint) (word & 0xffffffffull)); |
| 311 | + } catch (...) { |
| 312 | + // no token reached the queue, so no dispatch gate will ever retire |
| 313 | + // the cell - release it here or the slot is burned for the process |
| 314 | + if (word != 0) { |
| 315 | + cell.store(0, std::memory_order_release); |
| 316 | + } |
| 317 | + throw; |
| 318 | + } |
296 | 319 | return word; |
297 | 320 | } |
298 | 321 |
|
@@ -340,6 +363,10 @@ void EventLoop::ReleaseIdentifiedToken(jobject peer) { |
340 | 363 | env.DeleteGlobalRef(peer); |
341 | 364 | } |
342 | 365 |
|
| 366 | +jboolean EventLoop::ClaimTokenLegacy(JNIEnv* env, jclass clazz, jlong loopPtr, jlong cellWord) { |
| 367 | + return ClaimTokenCritical(loopPtr, cellWord); |
| 368 | +} |
| 369 | + |
343 | 370 | jboolean EventLoop::ClaimTokenCritical(jlong loopPtr, jlong cellWord) { |
344 | 371 | // @CriticalNative: no JNIEnv, thread stays runnable - a single CAS, no |
345 | 372 | // locks, no allocation, no exceptions. The loop pointer is valid for the |
|
0 commit comments