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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions