Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: upgrade data accesses to asynchrony #10

Open
ms5984 opened this issue Nov 29, 2023 · 1 comment
Open

feat: upgrade data accesses to asynchrony #10

ms5984 opened this issue Nov 29, 2023 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@ms5984
Copy link
Member

ms5984 commented Nov 29, 2023

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 ms5984 added the enhancement New feature or request label Nov 29, 2023
@ms5984 ms5984 self-assigned this Nov 29, 2023
@ms5984
Copy link
Member Author

ms5984 commented Dec 16, 2023

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)

  • isApplied - boolean
  • addPrecondition - Predicate<Data>
  • apply - CompletableFuture<Void>
    Can complete exceptionally with appropriate error. Always sets isApplied = true.
  • cancel
    Prevents the application of this write object (such as if this write layer is first passed to an Event before apply is called). Throws if isApplied = true.
  • copy - MutableData
    Creates a deep copy with isApplied, preconditions reset and cancel removed.

Implementation hints

  • cancel can be implemented as adding a Predicate with a _ -> false contract
  • a separate queue should be maintained for operation-sourced predicates. this simplifies the copy operation while maintaining the original semantics of the write layer's creation.
// 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant