// Previously used class "Q" (#1000934) used 328 bytes when idle. // Now we're at 32 (factor 10+). // And we can evaporate this even further by merging the queue // with the object it belongs to. Probably we'll end up paying nothing // for an idle object while still enjoying the experience of being // able to start a thread instantly whenever needed. sclass CompactQ extends Meta is AutoCloseable { // Object size at this point: 12 bytes for Java + 4 bytes for Meta // (assuming you have 32 GB or less of Java RAM) // // So let's keep saving space. // Step 1 - no name... well, no reserved name field, anyway. // Most queues can probably be identified from context. // if you want to give your event queue a name - put it in Meta // with these convenient procedures: S name() { ret or2((S) metaGet("name"), toString()); } void name(S name) { metaSet(+name); } // Object size now: Still 16 bytes unless you foolishly insist on a // custom queue name which you could definitely achieve in a much // more efficient way (hint _subclassing_). // Now we do need to expend 4 more bytes. The items in the queue // have to go somewhere. But! // No syncLinkedList or something - just an AppendableChain which // is a minimalist approach but above the honor level of elegance // and flexibility and absolutely not in the way in these circumstances. // It's all O(1) after all. // // All sync is done on the main object BTW. (CompactQ.this) AppendableChain q; // Object size now: 20b // retired also goes to meta. In the end you don't even notice a // difference. It's just another setter and getter and they are // clearly not hard to make either. // // Of course, you will notice that we store "unretired" as null // (meaning no entry at all, saving all the space) instead of false; // even in the case that someone retires their event queue and then // _unretires_ it. // // In the end, one can say we are really really pedantic but we do // have a style. synchronized bool retired() { ret metaGet("retired") != null; } synchronized void retired(bool b) { metaPut("retired", trueOrNull(b)); } // Object size: still 20b. Miraculous! // // ...Here comes a good one. // The "enter" field is an optional thread ownership marker, e.g. // for modules in the Java OS because otherwise thread control is // a nightmare. // // Well... it still kind of is. But I see the way forward with one // clean re-architecture that we should one day just do. We just need // central instances of thread managing. This cannot be decentra- // lized. // // Oh, and having slashed absolutely everything else, we keep this // one as an actual field. // // The field type is so a customer's enter procedure can do // whatever fancy things they fashion as long as they provide an // AutoCloseable that will be called for cleanup. IF0 enter; // New size: 24b // We do want to know who is servicing us currently... Thread thread; // 28b bool startingThread; // ...and finally the biggest boolean ever brings us to 32 bytes. // Optional monitoring ifdef CompactQ_Stats new AtomicLong bouts; // how often we have stopped and started int biggestSize; // how long the queue was at the worst moment endifdef AutoCloseable enter() { ret callF(enter); } synchronized bool running() { ret thread != null; } bool done() { ret !running() && (isEmpty() || retired() || !licensed()); } // This is way smarter than the half-awake sleep(1) method // Basically we take a place at the end of the line and when we're // at the counter we see if anyone came in behind us. And repeat // if necessary. // // Note that for this to work reliably we require that items in // the queue are auto-closed in case they can't be dispatched void waitUntilDone { while (!done()) putFlagInPipe(); } void putFlagInPipe() { flagSleep(flag -> add(raiseFlagOnRunAndClose(flag))); } // Yes, in JavaX functions can have any number of names public synchronized void close aka cancel() { retired(true); clear(); if (thread == null) ret; // threadBeingCancelled = new WeakReference(thread); // optional cancelAndInterruptThread(thread); thread = null; } // Forget all the entries but don't cancel or interrupt currently // executing task public void clear { L items; synchronized { items = cloneList(q); q = null; } cleanUp(items); } // append to q (do later) void add(Runnable r) { if (r == null) ret; synchronized { // cleanly reject if necessary if (retired()) ret with cleanUp(r); q = chainPlus(q, r); ifdef CompactQ_Stats biggestSize = max(size(), biggestSize); endifdef } checkForAction(); } // prepend to q (do next) void addInFront(Runnable r) { if (r == null) ret; synchronized { // cleanly reject if necessary if (retired()) ret with cleanUp(r); q = itemPlusChain(r, q); ifdef CompactQ_Stats biggestSize = max(size(), biggestSize); endifdef } checkForAction(); } protected void checkForAction() { synchronized { if (done() || running() || startingThread) ret; // There seems to be work, so we start a thread. set startingThread; } try { // At this point, we are no longer synchronized, but // we know we are uncontested. // No one else will try to start a thread. temp enter(); startThread(name(), () -> { try { temp enter(); // Put us in the book. synchronized { startingThread = false; thread = currentThread(); } // And off we go! _bout(); } finally { thread = null; startingThread = false; } }); } on fail { close(); } } synchronized Runnable _grabWork() { Runnable r = first(q); q = popFirst(q); ret r; } // work as long there is work void _bout { ifdef CompactQ_Stats inc(bouts); endifdef while (licensed() && !retired()) { Runnable r = _grabWork(); if (r != null) pcall { r.run(); } else { onIdle(); // Try once more because onIdle may have strategically waited // for 1 ms or something to see if more work came in. if ((r = _grabWork()) != null) pcall { r.run(); } // get back in the game else break; } } } synchronized bool isEmpty() { ret q == null; } synchronized int size() { ret l(q); } O mutex() { this; } // clients can synchronize on this // export procedure following JavaX synchronization rules // (only synchronize on one object at a time. exceptions granted // when proof of harmlessness is given.) synchronized L snapshot() { ret cloneList_unsynced(q); } // you can override this void onIdle {} }