Skip to content

I'm not sure whether "Java_org_jpl7_fli_Prolog_attach_1pool_1engine" actually unlocks the engine array in all cases #86

@dtonhofer

Description

@dtonhofer

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
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions