processing/processing-android — pull request #38 by wouter-vdb (2013-05-26 11:55:20+00:00)
Title: Proposal for a fix of a synchronisation issue with the dequeueing of the eventQueue.

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 dequeueEvents() from handleDraw()
concurrently with the main thread which called dequeueEvents() from
postEvent(), which is called from nativeMotionEvent(). Below you can
find a trace of a concrete situation.

I'm not deeply familiar with the implementation, but it seems that
postEvent() should not call 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[GLThread
10,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();
...
}
}
```