AVPKit
Mutex.cpp
1 /*******************************************************************************
2  * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved.
3  *
4  * This file is part of AVPKit.
5  *
6  * AVPKit is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * AVPKit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with AVPKit. If not, see <http://www.gnu.org/licenses/>.
18  *******************************************************************************/
19 
20 #include "Mutex.h"
21 #include "JNIHelper.h"
22 #include <cstdio>
23 
24 namespace com
25 {
26 namespace avpkit
27 {
28 namespace ferry
29 {
30 
31 jclass Mutex :: mClass = 0;
32 jmethodID Mutex :: mConstructorMethod = 0;
33 
34 bool Mutex :: mInitialized = false;
35 
36 Mutex :: Mutex()
37 {
38  mLock = 0;
39  mSpinCount = 0;
40 }
41 
42 Mutex :: ~Mutex()
43 {
44  JNIEnv *env = JNIHelper::sGetEnv();
45  if (env)
46  {
47  if (mSpinCount > 0)
48  {
49  // OK, this is bad; someone left us in a bad
50  // condition
51  fprintf(stderr, "Destroying monitor %p with non-zero spin count\n",
52  this);
53  while (mSpinCount > 0)
54  {
55  this->unlock();
56  }
57  }
58  if (mLock)
59  env->DeleteGlobalRef(mLock);
60  }
61  mLock = 0;
62 }
63 
64 bool
65 Mutex :: init()
66 {
67  if (!mInitialized)
68  {
69  JNIHelper::sRegisterInitializationCallback(initJavaBindings, 0);
70  mInitialized = true;
71  }
72  return mInitialized;
73 }
74 
75 void
76 Mutex :: initJavaBindings(JavaVM*, void*)
77 {
78  JNIEnv *env = JNIHelper::sGetEnv();
79  if (env && !mClass)
80  {
81  // We're inside a JVM, let's get to work.
82  jclass cls = env->FindClass("java/lang/Object");
83  if (cls)
84  {
85  // and find all our methods
86 
87  mConstructorMethod = env->GetMethodID(cls, "<init>", "()V");
88 
89  // keep a reference around
90  mClass = (jclass) env->NewWeakGlobalRef(cls);
91  }
92  }
93 }
94 
95 void
96 Mutex :: lock()
97 {
98  if (!mInitialized)
99  Mutex::init();
100 
101  if (mLock)
102  {
103  JNIEnv *env = JNIHelper::sGetEnv();
104  if (env)
105  {
106  //fprintf(stderr, " PRE-ENTER: %p\n", mLock);
107  if (env->ExceptionCheck())
108  {
109  // have a pending java exception; don't try the lock
110  throw std::runtime_error("pending java exception; not locking");
111  }
112  // testing that a thrown ERROR doesn't cause core-dumps
113  // throw std::runtime_error("bad panda; no shoots for you");
114 
115  if (env->MonitorEnter(mLock) != JNI_OK)
116  {
117  fprintf(stderr, "Could not enter lock: %p\n", mLock);
118  throw std::runtime_error("failed to enter monitor; not locking");
119  }
120  else
121  {
122  if (env->ExceptionCheck()) {
123  env->MonitorExit(mLock);
124  throw std::runtime_error("failed to enter monitor due to "
125  "pending exception; not locking");
126  } else {
127  // we're in the lock now!
128  ++mSpinCount;
129  }
130  }
131  //fprintf(stderr, "POST-ENTER: %p\n", mLock);
132  }
133  }
134 }
135 
136 void
137 Mutex :: unlock()
138 {
139  if (!mInitialized)
140  Mutex::init();
141 
142  if (mLock)
143  {
144  JNIEnv *env = JNIHelper::sGetEnv();
145  if (env)
146  {
147  //fprintf(stderr, " PRE-EXIT: %p\n", mLock);
148  if (mSpinCount <= 0)
149  {
150  // we called unlock without a matching successful lock;
151  // we must fail.
152  throw std::runtime_error("unlock attempt on unlocked mutex");
153  }
154  // reduce the spin count regardless of result while still under
155  // the lock
156  --mSpinCount;
157 
158  if (env->MonitorExit(mLock) != JNI_OK)
159  {
160  // this can fail in out of memory situations but there's not much
161  // we can do.
162  //fprintf(stderr, "Could not exit lock: %p\n", mLock);
163  throw std::runtime_error("failed attempt to unlock mutex");
164  }
165  //fprintf(stderr, " POST-EXIT: %p\n", mLock);
166  }
167  }
168 }
169 
170 Mutex*
171 Mutex :: make()
172 {
173  Mutex* retval = 0;
174  jobject newValue = 0;
175  JNIEnv *env = 0;
176 
177  try
178  {
179  if (!mInitialized)
180  Mutex::init();
181 
182  env = JNIHelper::sGetEnv();
183  if (env)
184  {
185  if (!mClass)
186  {
187  // we're being called BEFORE our initialization
188  // callback was called, but during someone
189  // elses initialization route, so we can be
190  // sure we have a JVM.
191  Mutex::initJavaBindings(JNIHelper::sGetVM(), 0);
192  }
193  if (mClass && mConstructorMethod)
194  {
195  retval = new Mutex();
196  if (!retval)
197  throw std::bad_alloc();
198  retval->acquire();
199 
200  if (env->ExceptionCheck())
201  throw std::bad_alloc();
202 
203  // we're running within Java
204  newValue = env->NewObject(mClass, mConstructorMethod);
205  if (!newValue)
206  throw std::bad_alloc();
207 
208  if (env->ExceptionCheck())
209  throw std::bad_alloc();
210 
211  retval->mLock = env->NewGlobalRef(newValue);
212 
213  if (!retval->mLock)
214  throw std::bad_alloc();
215 
216  if (env->ExceptionCheck())
217  throw std::bad_alloc();
218  env->DeleteLocalRef(newValue);
219  newValue = 0;
220  }
221  }
222  }
223  catch (std::bad_alloc & e)
224  {
225  VS_REF_RELEASE(retval);
226  throw e;
227  }
228  catch (std::exception &e)
229  {
230  fprintf(stderr, "got uncaught error creating mutex: %p", retval);
231  VS_REF_RELEASE(retval);
232  }
233  if (env && newValue)
234  env->DeleteLocalRef(newValue);
235  return retval;
236 }
237 }
238 }
239 }
WARNING: Do not use logging in this class, and do not set any static file variables to values other t...