@@ -3781,23 +3781,142 @@ JNIEXPORT void JNICALL RXTXPort(setflowcontrol)( JNIEnv *env,
37813781 return ;
37823782}
37833783
3784- /*----------------------------------------------------------
3785- unlock_monitor_thread
3784+ /**
3785+ * Write-lock the monitor thread to protect state mutation.
3786+ *
3787+ * Blocks until the write lock comes available.
3788+ *
3789+ * @param [in] env the JNI environment
3790+ * @param [in] obj an RXTXPort instance
3791+ * @return 0 on success; 1 on error
3792+ */
3793+ int lock_monitor_thread (JNIEnv * env , jobject jobj )
3794+ {
3795+ jfieldID monitorThreadStateWriteLockField = (* env )-> GetFieldID (
3796+ env ,
3797+ (* env )-> GetObjectClass (env , jobj ),
3798+ "monitorThreadStateWriteLock" ,
3799+ "Ljava/util/concurrent/locks/Lock;" );
3800+ if ((* env )-> ExceptionCheck (env )) {
3801+ return 1 ;
3802+ }
37863803
3787- accept: event_info_struct
3788- perform: unlock the monitor thread so event notification can start.
3789- return: none
3790- exceptions: none
3791- comments: Events can be missed otherwise.
3792- ----------------------------------------------------------*/
3804+ jobject monitorThreadStateWriteLock = (* env )-> GetObjectField (
3805+ env ,
3806+ jobj ,
3807+ monitorThreadStateWriteLockField );
3808+ if ((* env )-> ExceptionCheck (env )) {
3809+ return 1 ;
3810+ }
37933811
3794- void unlock_monitor_thread ( struct event_info_struct * eis )
3812+ jmethodID lock = (* env )-> GetMethodID (
3813+ env ,
3814+ (* env )-> GetObjectClass (env , monitorThreadStateWriteLock ),
3815+ "lock" ,
3816+ "()V" );
3817+ if ((* env )-> ExceptionCheck (env )) {
3818+ return 1 ;
3819+ }
3820+
3821+ (* env )-> CallVoidMethod (env , monitorThreadStateWriteLock , lock );
3822+ if ((* env )-> ExceptionCheck (env )) {
3823+ return 1 ;
3824+ }
3825+
3826+ return 0 ;
3827+ }
3828+
3829+ /**
3830+ * Signal that the monitor thread is ready for work.
3831+ *
3832+ * In order to signal the condition, the current thread must already hold the
3833+ * write lock.
3834+ *
3835+ * @param [in] env the JNI environment
3836+ * @param [in] obj an RXTXPort instance
3837+ * @return 0 on success; 1 on error
3838+ */
3839+ int signal_monitor_thread_ready (JNIEnv * env , jobject jobj )
37953840{
3796- JNIEnv * env = eis -> env ;
3797- jobject jobj = * (eis -> jobj );
3841+ jfieldID monitorThreadReadyField = (* env )-> GetFieldID (
3842+ env ,
3843+ (* env )-> GetObjectClass (env , jobj ),
3844+ "monitorThreadReady" ,
3845+ "Ljava/util/concurrent/locks/Condition;" );
3846+ if ((* env )-> ExceptionCheck (env )) {
3847+ return 1 ;
3848+ }
37983849
3799- jfieldID jfid = (* env )-> GetFieldID ( env , (* env )-> GetObjectClass ( env , jobj ), "MonitorThreadLock" , "Z" );
3800- (* env )-> SetBooleanField ( env , jobj , jfid , (jboolean ) 0 );
3850+ jobject monitorThreadReady = (* env )-> GetObjectField (
3851+ env ,
3852+ jobj ,
3853+ monitorThreadReadyField );
3854+ if ((* env )-> ExceptionCheck (env )) {
3855+ return 1 ;
3856+ }
3857+
3858+ jmethodID signal = (* env )-> GetMethodID (
3859+ env ,
3860+ (* env )-> GetObjectClass (env , monitorThreadReady ),
3861+ "signal" ,
3862+ "()V" );
3863+ if ((* env )-> ExceptionCheck (env )) {
3864+ return 1 ;
3865+ }
3866+
3867+ (* env )-> CallVoidMethod (env , monitorThreadReady , signal );
3868+ if ((* env )-> ExceptionCheck (env )) {
3869+ return 1 ;
3870+ }
3871+
3872+ return 0 ;
3873+ }
3874+
3875+ /**
3876+ * Unlock the write lock on the monitor thread to permit read and write access
3877+ * by other threads.
3878+ *
3879+ * In order to unlock the monitor thread, the current thread must already hold
3880+ * the write lock.
3881+ *
3882+ * @param [in] env the JNI environment
3883+ * @param [in] obj an RXTXPort instance
3884+ * @return 0 on success; 1 on error
3885+ */
3886+ int unlock_monitor_thread (JNIEnv * env , jobject jobj )
3887+ {
3888+ jfieldID monitorThreadStateWriteLockField = (* env )-> GetFieldID (
3889+ env ,
3890+ (* env )-> GetObjectClass (env , jobj ),
3891+ "monitorThreadStateWriteLock" ,
3892+ "Ljava/util/concurrent/locks/Lock;" );
3893+ if ((* env )-> ExceptionCheck (env )) {
3894+ return 1 ;
3895+ }
3896+
3897+ jobject monitorThreadStateWriteLock = (* env )-> GetObjectField (
3898+ env ,
3899+ jobj ,
3900+ monitorThreadStateWriteLockField );
3901+ if ((* env )-> ExceptionCheck (env )) {
3902+ return 1 ;
3903+ }
3904+
3905+ jmethodID unlock = (* env )-> GetMethodID (
3906+ env ,
3907+ (* env )-> GetObjectClass (env , monitorThreadStateWriteLock ),
3908+ "unlock" ,
3909+ "()V" );
3910+ if ((* env )-> ExceptionCheck (env )) {
3911+ return 1 ;
3912+ }
3913+
3914+ (* env )-> CallVoidMethod (env , monitorThreadStateWriteLock , unlock );
3915+ if ((* env )-> ExceptionCheck (env )) {
3916+ return 1 ;
3917+ }
3918+
3919+ return 0 ;
38013920}
38023921
38033922/*----------------------------------------------------------
@@ -4243,6 +4362,8 @@ RXTXPort.eventLoop
42434362----------------------------------------------------------*/
42444363JNIEXPORT void JNICALL RXTXPort (eventLoop )( JNIEnv * env , jobject jobj )
42454364{
4365+ if (lock_monitor_thread (env , jobj )) goto end ;
4366+
42464367#ifdef WIN32
42474368 int i = 0 ;
42484369#endif /* WIN32 */
@@ -4255,7 +4376,10 @@ JNIEXPORT void JNICALL RXTXPort(eventLoop)( JNIEnv *env, jobject jobj )
42554376 ENTER ( "eventLoop\n" );
42564377 if ( !initialise_event_info_struct ( & eis ) ) goto end ;
42574378 if ( !init_threads ( & eis ) ) goto end ;
4258- unlock_monitor_thread ( & eis );
4379+
4380+ if (signal_monitor_thread_ready (env , jobj )) goto end ;
4381+ if (unlock_monitor_thread (env , jobj )) goto end ;
4382+
42594383 do {
42604384 report_time_eventLoop ( );
42614385 do {
@@ -4271,13 +4395,18 @@ JNIEXPORT void JNICALL RXTXPort(eventLoop)( JNIEnv *env, jobject jobj )
42714395 * drain loop). That will also set `eis.closing`, so the
42724396 * function will return as usual in the next block.
42734397 *
4274- * Note that `nativeavailable()` has thrown an exception at this
4275- * point, so we need to be careful about calling further JNI
4276- * functions. Conventional wisdom states that JNI functions
4277- * called when an exception is pending “may lead to unexpected
4278- * results”. Empirically, this seems to work okay; I suspect, at
4279- * worst, the functions might return an error. */
4398+ * `nativeavailable()` has thrown an exception at this point, so
4399+ * in order to call anything else which uses exceptions (like
4400+ * the locking functions), we'll temporarily clear the exception
4401+ * then reset it before continuing. */
4402+ jthrowable hardwareException = (* env )-> ExceptionOccurred (env );
4403+ (* env )-> ExceptionClear (env );
4404+
4405+ if (lock_monitor_thread (env , jobj )) goto end ;
42804406 RXTXPort (interruptEventLoop )(env , jobj );
4407+ if (unlock_monitor_thread (env , jobj )) goto end ;
4408+
4409+ (* env )-> Throw (env , hardwareException );
42814410 }
42824411 /* nothing goes between this call and select */
42834412 if ( eis .closing )
@@ -4962,7 +5091,22 @@ JNIEXPORT void JNICALL RXTXPort(interruptEventLoop)(JNIEnv *env,
49625091 usleep (1000 );
49635092 }
49645093 }
5094+
49655095 index -> eventloop_interrupted = 1 ;
5096+
5097+ jfieldID monThreadisInterruptedField = (* env )-> GetFieldID (
5098+ env ,
5099+ (* env )-> GetObjectClass (env , jobj ),
5100+ "monThreadisInterrupted" ,
5101+ "Z" );
5102+ if ((* env )-> ExceptionCheck (env )) {
5103+ return ;
5104+ }
5105+ (* env )-> SetBooleanField (env , jobj , monThreadisInterruptedField , JNI_TRUE );
5106+ if ((* env )-> ExceptionCheck (env )) {
5107+ return ;
5108+ }
5109+
49665110 /*
49675111 Many OS's need a thread running to determine if output buffer is
49685112 empty. For Linux and Win32 it is not needed. So closing is used to
0 commit comments