processing / processing-android-archive

96 stars 50 forks source link

Proposal for a fix of a synchronisation issue with the dequeueing of the eventQueue in android-core. #38

Closed wouter-vdb closed 10 years ago

wouter-vdb commented 11 years ago

I had a consistent problem while handling touch-events in an application that has noLoop() in setup() and uses redraw() whenever the screen needs to be redrawn after a relevant touch-event. The exception "Nothing left on the event queue" was thrown from InternalEventQueue.remove() at arbitrary moments.

The problem was that two threads tried to dequeue the eventsQueue concurrently. The GLThread called PApplet.dequeueEvents() from PApplet.handleDraw() concurrently with the main thread which called PApplet.dequeueEvents() from PApplet.postEvent(), which is called from PApplet.nativeMotionEvent(). Below you can find a trace of a concrete situation.

I'm not deeply familiar with the implementation, but it seems that PApplet.postEvent() should not call PApplet.dequeueEvents() when either looping or redraw is true. This would be fixed as follows:

public void postEvent(processing.event.Event pe) {
  eventQueue.add(pe);
-  if (!looping) {
+  if (!looping && !redraw) {
    dequeueEvents();
  }
}

It might also be necessary to synchronize dequeueEvents() to avoid a potential race condition when postEvent() in the main thread checks the (!looping && !redraw) condition right before redraw is set to true in redraw(). The main thread would then continue with dequeueEvents() while the GLThread would at some point call handleDraw() which would call dequeueEvents() concurrently.

02 x 05-26 10:51:49.267: I/System.out(8800): >> handleDraw() - count: 0, offset: 0, thread: Thread[GLThread 10,5,main]
03   05-26 10:51:49.277: I/System.out(8800): >> nativeMotionEvent() - count: 0, offset: 0, thread: Thread[main,5,main]
04   05-26 10:51:49.277: I/System.out(8800): >> postEvent() - count: 0, offset: 0, thread: Thread[main,5,main]
05   05-26 10:51:49.277: I/System.out(8800): >> InternalEventQueue.add() - count: 0, offset: 0, thread: Thread[main,5,main]
06   05-26 10:51:49.277: I/System.out(8800): << InternalEventQueue.add() - count: 1, offset: 0, thread: Thread[main,5,main]
07 x 05-26 10:51:49.287: I/System.out(8800): >> dequeueEvents() - count: 1, offset: 0, thread: Thread[GLThread 10,5,main]
08   05-26 10:51:49.287: I/System.out(8800):  - looping: false, thread: Thread[main,5,main]
09 x 05-26 10:51:49.287: I/System.out(8800): >> InternalEventQueue.available() - count: 1, offset: 0, thread: Thread[GLThread 10,5,main]
10   05-26 10:51:49.287: I/System.out(8800): >> dequeueEvents() - count: 1, offset: 0, thread: Thread[main,5,main]
11 x 05-26 10:51:49.287: I/System.out(8800):  - while-step in dequeueEvents() - count: 1, offset: 0, thread: Thread[GLThread10,5,main]
12   05-26 10:51:49.287: I/System.out(8800): >> InternalEventQueue.available() - count: 1, offset: 0, thread: Thread[main,5,main]
13   05-26 10:51:49.287: I/System.out(8800):  - while-step in dequeueEvents() - count: 1, offset: 0, thread: Thread[main,5,main]
14 x 05-26 10:51:49.297: I/System.out(8800): >> InternalEventQueue.remove() - count: 1, offset: 0, thread: Thread[GLThread 10,5,main]
15 x 05-26 10:51:49.297: I/System.out(8800): << InternalEventQueue.remove() - count: 0, offset: 0, thread: Thread[GLThread 10,5,main]
16 x 05-26 10:51:49.297: I/System.out(8800): >> InternalEventQueue.available() - count: 0, offset: 0, thread: Thread[GLThread 10,5,main]
17   05-26 10:51:49.297: I/System.out(8800): >> InternalEventQueue.remove() - count: 0, offset: 0, thread: Thread[main,5,main]

This trace is produced by these trace instructions:

public void handleDraw() {
  println(">> handleDraw() - count: " + eventQueue.count + ", offset: "
+ eventQueue.offset + ", thread: " + Thread.currentThread());
  ...
}

protected void nativeMotionEvent(android.view.MotionEvent motionEvent) {
  println(">> nativeMotionEvent() - count: " + eventQueue.count + ",
offset: " + eventQueue.offset + ", thread: " + Thread.currentThread());
  ...
}

class InternalEventQueue {
  ...
  synchronized void add(Event e) {
    println(">> InternalEventQueue.add() - count: " + count + ",
offset: " + offset + ", thread: " + Thread.currentThread());
    if (count == queue.length) {
      queue = (Event[]) expand(queue);
    }
    queue[count++] = e;
    println("<< InternalEventQueue.add() - count: " + count + ",
offset: " + offset + ", thread: " + Thread.currentThread());
  }

  synchronized Event remove() {
    println(">> InternalEventQueue.remove() - count: " + count + ",
offset: " + offset + ", thread: " + Thread.currentThread());
    if (offset == count) {
      throw new RuntimeException("Nothing left on the event queue.");
    }
    Event outgoing = queue[offset++];
    if (offset == count) {
      // All done, time to reset
      offset = 0;
      count = 0;
    }
    println("<< InternalEventQueue.remove() - count: " + count + ",
offset: " + offset + ", thread: " + Thread.currentThread());
    return outgoing;
  }

  synchronized boolean available() {
    println(">> InternalEventQueue.available() - count: " + count + ",
offset: " + offset + ", thread: " + Thread.currentThread());
    return count != 0;
  }
}

public void postEvent(processing.event.Event pe) {
  println(">> postEvent() - count: " + eventQueue.count + ", offset: "
+ eventQueue.offset + ", thread: " + Thread.currentThread());
  eventQueue.add(pe);
  println(" - after add in postEvent() - count: " + eventQueue.count +
", offset: " + eventQueue.offset + ", thread: " +
Thread.currentThread());
  println(" - looping: " + looping + ", thread: " +
Thread.currentThread());

  if (!looping) {
    dequeueEvents();
  }
}

protected void dequeueEvents() {
  println(">> dequeueEvents() - count: " + eventQueue.count + ",
offset: " + eventQueue.offset + ", thread: " + Thread.currentThread());
  //boolean available = eventQueue.available();
  while (eventQueue.available()) {
    println(" - while-step in dequeueEvents() - count: " +
eventQueue.count + ", offset: " + eventQueue.offset + ", thread: " +
Thread.currentThread());
    Event e = eventQueue.remove();
    ...
  }
}
wouter-vdb commented 11 years ago

An example application in which this issue occurs can be found at https://github.com/wouter-vdb/multec-cp-processing/tree/master/android/PEA_Basics_02. When using android-core without the proposed fix, this application crashes consistently when you interact with the cube for a bit.

benfry commented 11 years ago

@codeanticode can you take a look at this–I'm not sure offhand why GL would be interfering like this.

codeanticode commented 10 years ago

@wouter-vdb this pull request is obsolete since the original processing-android repo has been archived. However, I tested the following test code:

public class MainActivity extends PApplet {

    public int sketchQuality() {
      return 2;
    }

    public String sketchRenderer() {
        return P3D;
    }

    public int sketchWidth() {
        return displayWidth;
    }

    public int sketchHeight() {
        return displayHeight;
    }

    public void setup() {
      fill(0, 255, 0);
      noLoop();
    }

    public void draw() {
      background(150);

      translate(mouseX, mouseY);
      rotateX(frameCount * 0.01f);
      rotateY(frameCount * 0.01f);      
      lights();
      box(200);
    }

    public void mouseDragged() {
      redraw();
    }
}

from Eclipse and didn't observe any event queue exceptions, without using the proposed changes. Please open an issue on the new processing-android repo if this issue is still happening to you. Sorry for the late response.