Javadoc
The following classes and CompletableThreadPoolExecutor
CompletableRejectedExecutionHandler exist exclusively to enable the
desired behavior for awaitCompletion. The desired behavior is that at any
moment one can call awaitCompletion and it will block until all tasks
added up to that point have completed (and if flushAndWait was called, all
documents added up to that point are written to the database). This is
tricky behavior because tasks will continue to be added asynchronously
after that point, but we only want to wait for the completion of tasks
added up to that point.
This behavior is desired so a developer can add a document to a
WriteBatcher instance and call awaitCompletion to know that document and
all documents added previously are commited in the database when
awaitCompletion returns (assuming it didn't timeout). While a developer
could achieve the same behavior asynchronously by checking for documents
in the onBatchSuccess callback, that logic is complex, so we're taking on
that burden for them.
To achieve this behavior we keep a set of all tasks
(queuedAndExecutingTasks) and we snapshot that set at the point
awaitCompletion is called. Then we loop through all tasks in the
snapshot, waiting for each to complete. We know a task has completed when
it has been removed from the snapshot. We use Object methods wait and
notify so we know when to re-check the snapshot to see if a task has been
removed.
A task can complete three ways:
1) Normal execution by a thread from the thread pool
2) Rejected execution because the task queue is full. We use
CallerRunsPolicy so the calling thread performs execution.
3) Shutdown of the thread pool
After each of the three cases we remove the task from
queuedAndExecutingTasks and from any active snapshots. This avoids
accumulation of tasks after they're finished. To avoid accumulation of
snapshots they remove themselves when they are no longer needed (when the
awaitCompletion call is finished).