-
Notifications
You must be signed in to change notification settings - Fork 35
Description
I have let the magic of Eclipse C IDE work on jpl.c.
It complained that Java_org_jpl7_fli_Prolog_attach_1pool_1engine does not always properly return a value. It is right.
Then I noticed that with some likelihood, the engines array is not properly unlocked under circumstances of error.
The code was hard to read so I reengineered it as below (using a state variable instead of a goto just feels better).
It compiles but I haven't tried to run it yet (so there is some likelihood of non-success). Well, tell me what you think or if it's of any use.
(It seems that the C code tries to stay conservatively in before-C99 style, feels very "automotive industry"?. Also, the restriction on 80 columns is very old-school.)
Enough grumbling:
/*
* Some signaling constants used soon. They implement a state machine (sort of)
*/
static const int SUCCESS_AND_UNLOCKED = 1;
static const int FAILURE_AND_UNLOCKED = 2;
static const int TRY_NEXT_STILL_LOCKED = 3;
/*
* Class: org_jpl7_fli_Prolog
* Method: attach_pool_engine
* Signature: ()Lorg/jpl7/fli/engine_t;
*/
JNIEXPORT jobject JNICALL
Java_org_jpl7_fli_Prolog_attach_1pool_1engine(JNIEnv *env, jclass jProlog) {
if (!jpl_ensure_pvm_init(env)) {
return NULL; // FIXME: throw exception (from Java)
}
// Find a Prolog engine to use. Try setting each of the engines in the pool.
// If they are all in use, wait for the condition variable and then try again.
// FIXME: Shouldn't there be at least a timeout?
LOCK_ENGINES();
jobject rval;
int res = TRY_NEXT_STILL_LOCKED;
while (res == TRY_NEXT_STILL_LOCKED) {
int empty_pos = -1;
for (int i = 0; ((i < engines_allocated) && (res == TRY_NEXT_STILL_LOCKED)); i++) {
if (engines[i] != NULL) {
// Found a live engine. Attaching to it may or may not succeed
// On success, the lock on engine[i] has been released already.
res = try_attaching_to_pool_engine(i, env, &rval);
}
else {
if (empty_pos == -1) {
empty_pos = i; // retain earliest free entry for reallocation
}
}
}
if (res == TRY_NEXT_STILL_LOCKED) {
// We ran out of engines; try allocating a new one on a free place
if (empty_pos >= 0) {
// There still is a free place; the engine becomes non-null on success
create_pool_engine(empty_pos);
if (engines[empty_pos] == NULL) {
// failed to created engine; busted for good!
UNLOCK_ENGINES();
res = FAILURE_AND_UNLOCKED;
}
}
else {
unlock_engines_and_wait_patiently();
}
}
}
if (res == SUCCESS_AND_UNLOCKED) {
return rval;
}
else {
assert(res == FAILURE_AND_UNLOCKED);
return NULL; // TODO Java should raise an exception
}
}
/* unlock engines and wait patiently */
static void
unlock_engines_and_wait_patiently() {
DEBUG(1, Sdprintf("JPL no engines ready; waiting...\n"));
#ifdef USE_WIN_EVENTS
UNLOCK_ENGINES();
WaitForSingleObject(engines_event, 1000);
LOCK_ENGINES();
#else
pthread_cond_wait(&engines_cond, &engines_mutex);
#endif
}
/* create an engine at position i (we have the lock on engines) */
static void
create_pool_engine(int i)
{
// https://eu.swi-prolog.org/pldoc/doc_for?object=c(%27PL_create_engine%27)
engines[i] = PL_create_engine(NULL);
if (engines[i] == NULL) {
Sdprintf("JPL: Failed to create engine %d\n", i);
}
else {
DEBUG(1, Sdprintf("JPL created engine[%d]=%p\n", i, engines[i]));
}
}
/*
* Call PL_set_engine on engine i to own it; if this succeeds, immediately
* release the lock on engines[]. Returns NULL or pointer to the engine (jEngineT_c)
*/
static int
try_attaching_to_pool_engine(int i, JNIEnv *env, jobject *rval)
{
DEBUG(1, Sdprintf("JPL trying engine[%d]=%p\n", i, engines[i]));
// https://eu.swi-prolog.org/pldoc/doc_for?object=c(%27PL_set_engine%27)
int rc = PL_set_engine(engines[i], NULL);
if (rc == PL_ENGINE_SET) {
DEBUG(1,Sdprintf("JPL attaching engine[%d]=%p\n", i, engines[i]));
UNLOCK_ENGINES();
// https://docs.oracle.com/en/java/javase/14/docs/specs/jni/functions.html#allocobject
*rval = (*env)->AllocObject(env, jEngineT_c);
if (*rval != NULL) {
setPointerValue(env, *rval, (pointer) engines[i]);
return SUCCESS_AND_UNLOCKED;
}
else {
// I don't know what this does:
PL_set_engine(NULL, NULL);
return FAILURE_AND_UNLOCKED;
}
}
else if (rc == PL_ENGINE_INUSE) {
return TRY_NEXT_STILL_LOCKED;
}
else {
DEBUG(1,Sdprintf("JPL PL_set_engine %d fails with %d\n", i, rc));
UNLOCK_ENGINES();
return FAILURE_AND_UNLOCKED;
}
}vs the original which is compressed as for embedded MCUs, but hard to ascertain:
/*
* Class: org_jpl7_fli_Prolog
* Method: attach_pool_engine
* Signature: ()Lorg/jpl7/fli/engine_t;
*/
JNIEXPORT jobject JNICALL
Java_org_jpl7_fli_Prolog_attach_1pool_1engine(JNIEnv *env, jclass jProlog)
{ jobject rval;
int i;
if ( !jpl_ensure_pvm_init(env) )
return NULL; /* FIXME: throw exception */
/* Find an engine. Try setting each of the engines in the pool. */
/* If they are all in use wait for the condition variable and try again. */
LOCK_ENGINES();
for (;;)
{ try_again:
for (i = 0; i < engines_allocated; i++)
{ int rc;
if ( !engines[i] )
continue;
DEBUG(1, Sdprintf("JPL trying engine[%d]=%p\n", i, engines[i]));
if ((rc = PL_set_engine(engines[i], NULL)) == PL_ENGINE_SET)
{ DEBUG(1, Sdprintf("JPL attaching engine[%d]=%p\n", i, engines[i]));
UNLOCK_ENGINES();
if ( (rval = (*env)->AllocObject(env, jEngineT_c)) )
{ setPointerValue(env, rval, (pointer)engines[i]);
return rval;
}
PL_set_engine(NULL, NULL);
return NULL;
}
if ( rc != PL_ENGINE_INUSE )
{ DEBUG(1, Sdprintf("JPL PL_set_engine %d fails with %d\n", i, rc));
UNLOCK_ENGINES();
return NULL; /* bad engine status: oughta throw exception */
}
}
for (i = 0; i < engines_allocated; i++)
{ if ( !engines[i] )
{ if ( !(engines[i] = PL_create_engine(NULL)) )
{ Sdprintf("JPL: Failed to create engine %d\n", i);
return NULL;
}
DEBUG(1, Sdprintf("JPL created engine[%d]=%p\n", i, engines[i]));
goto try_again;
}
}
DEBUG(1, Sdprintf("JPL no engines ready; waiting...\n"));
#ifdef USE_WIN_EVENTS
UNLOCK_ENGINES();
WaitForSingleObject(engines_event, 1000);
LOCK_ENGINES();
#else
pthread_cond_wait(&engines_cond, &engines_mutex);
#endif
}
}