the-h-team / clans

An alternative to Factions with unique features & chunk based claiming
Apache License 2.0
4 stars 3 forks source link

feat: upgrade data accesses to asynchrony #10

Open ms5984 opened 1 year ago

ms5984 commented 1 year ago

Abstract

An all-too-common challenge among plugins looking to support external data sources is the unfortunate need to resolve the consequences of sharing a resource between threads. The Bukkit API's affinity for the main thread provides a very convenient way for us to predict how most resources will be shared--if code is called on the main thread, it will always be called in sequence, never concurrently.

This convenience comes with a price--if something takes too long, it can't be done on the main thread without blocking everything else and effectively stalling the server. Originally, Clans loads everything from files at the start so that things don't take too long--but what if files, preloading are not an option? If we update data, how do we know when it's been fully applied? This is where asynchrony must come into play.

Feature

Elements of Clans and its API currently access data in a blocking fashion with certain guarantees:

  1. All data is loaded at startup
  2. We know about and direct all data updates
  3. Data is written to cache; the saving is handled at shutdown
  4. Data written to the cache is applied instantly, allowing us to make assumptions as to the state of the cache based on the order of task executions

All of these points (and particularly the last one) must be addressed by any new non-blocking API.

Design considerations:

  1. We can still preload. All code in getter hot-paths must be cached values with an acceptable time-to-live.
  2. For writes we must snapshot and validate state during all updates so that business logic can (and must) anticipate conditions of invalid state.
  3. The cache must be managed internally. Writes will propagate to the cache on success. Writing and saving are combined.
  4. No assumptions about the state of the cache can be made. Each write operation will obtain a lock on its particular resource and support optionally maintaining this lock until dependent business logic has completed its tasks.
ms5984 commented 11 months ago

Concept

The new API separates reads and writes, requiring a context switch to access write functionality. The idea with these write layers is that the updates that they accept will not immediately write through to the data source. Additionally, the write layer will allow the expected original state of the clan data to be validated as a prerequisite to a write being applied.

Write layer methods (abstract)

Implementation hints

// operation-sourced predicate example
Write w = clan.asMutable();
w.addPower(3.0d);
// This automatically adds the following predicate, internally, like so
w.osp.add(data -> data.getPower() != w.power);
// Only after this is w.power updated