| /* |
| * Copyright 2026 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| package org.webrtc; |
| |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| /** |
| * Thread-safe lifecycle lock for WebRTC native object wrappers. |
| * Guards native pointer access with a read-lock and destruction with a write-lock, |
| * preventing Use-After-Free race conditions between JNI method calls and dispose(). |
| */ |
| class NativeLifecycleLock { |
| public interface NativeCallable<T> { |
| T call(); |
| } |
| |
| public interface NativePointerCallable<T> { |
| T call(long nativePointer); |
| } |
| |
| public interface NativeRunnable { |
| void run(); |
| } |
| |
| public interface NativePointerRunnable { |
| void run(long nativePointer); |
| } |
| |
| public interface NativeDisposer { |
| void dispose(long nativePointer); |
| } |
| |
| private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); |
| private final String className; |
| private long nativePointer; |
| |
| NativeLifecycleLock(String className, long nativePointer) { |
| this.className = className; |
| this.nativePointer = nativePointer; |
| } |
| |
| <T> T call(NativeCallable<T> callable) { |
| lock.readLock().lock(); |
| try { |
| checkNotDisposed(); |
| return callable.call(); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| <T> T call(NativePointerCallable<T> callable) { |
| lock.readLock().lock(); |
| try { |
| checkNotDisposed(); |
| return callable.call(nativePointer); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| void run(NativeRunnable runnable) { |
| lock.readLock().lock(); |
| try { |
| checkNotDisposed(); |
| runnable.run(); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| void run(NativePointerRunnable runnable) { |
| lock.readLock().lock(); |
| try { |
| checkNotDisposed(); |
| runnable.run(nativePointer); |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| boolean isDisposed() { |
| lock.readLock().lock(); |
| try { |
| return nativePointer == 0; |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| void dispose(NativeDisposer disposer) { |
| if (isDisposed()) { |
| return; |
| } |
| lock.writeLock().lock(); |
| try { |
| if (nativePointer != 0) { |
| disposer.dispose(nativePointer); |
| nativePointer = 0; |
| } |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| |
| long getNativePointer() { |
| lock.readLock().lock(); |
| try { |
| checkNotDisposed(); |
| return nativePointer; |
| } finally { |
| lock.readLock().unlock(); |
| } |
| } |
| |
| private void checkNotDisposed() { |
| if (nativePointer == 0) { |
| throw new IllegalStateException(className + " has been disposed."); |
| } |
| } |
| } |