8000 Add preemption logic to GlobalState and typecheck by jvilk-stripe · Pull Request #2407 · sorbet/sorbet · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add preemption logic to GlobalState and typecheck #2407

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

Merged
merged 7 commits into from
Jan 15, 2020

Conversation

jvilk-stripe
Copy link
Collaborator
@jvilk-stripe jvilk-stripe commented Jan 3, 2020

Add preemption logic to GlobalState and typecheck. The new logic is currently unused.

This change introduces two new classes:

  • TypecheckEpochManager: Contains all of the logic related to slow path cancelation. Previously, all of this lived directly in GlobalState.
  • PreemptionTaskManager: Contains all of the logic related to preemption. Passed into the typecheck method.

Motivation

This logic is needed to enable requests to preempt the slow path. The flow goes like this:

  • The typechecking thread is currently running a slow path.
  • The message processing thread attempts to preempt the slow path with a task (trySchedulePreemptionTask), which succeeds only if the slow path is still happening.
    • N.B.: withEpochLock protects against races between the slow path ending and a preemption task getting scheduled.
  • The typechecking thread, within pipeline::typecheck, attempts to run a preemption task with each turn of the coordination loop (it wakes up with maximum latency ~20ms) using tryRunScheduledPreemptionTask.
    • If a task is registered, tryRunScheduledPreemptionTask grabs typecheckMutex as a write lock (exclusive lock). Worker threads that are typechecking individual files conduct their work while holding a read lock (shared lock) on typecheckMutex via lockPreemption. These threads periodically reacquire the lock, and will block if a thread is attempting to acquire a write lock. Thus, the preemption task only begins once all worker threads are blocked and are not performing any work.

Test plan

I added automated tests for the core GlobalState logic. There's no easy way to test typecheck itself. I intend to add end-to-end tests for this mechanism once I hook up LSP to the preemption logic.

jvilk-stripe requested a review from a team January 3, 2020 00:29
@ghost ghost requested review from aisamanra and removed request for a team January 3, 2020 00:29
ast::ParsedFilesOrCancelled typecheck(std::unique_ptr<core::GlobalState> &gs, std::vector<ast::ParsedFile> what,
const options::Options &opts, WorkerPool &workers);
const options::Options &opts, WorkerPool &workers, bool cancelable = false,
Copy link
Collaborator Author
@jvilk-stripe jvilk-stripe Jan 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancelable is now an explicit parameter because the fast path calls typecheck during preemption but is not cancelable.

It prevents issues with the following interleaving:

  • Preemption task is scheduled.
  • Slow path is canceled.
  • Preemption task runs, sees that wasTypecheckingCanceled is true, and aborts fast path.

@jvilk-stripe jvilk-stripe requested review from mkillianey-stripe and removed request for aisamanra January 3, 2020 00:31
@jvilk-stripe
Copy link
Collaborator Author

cc @DarkDimius -- you might have strong opinions on this part of the code, as it impacts the core GlobalState abstraction.

// [IDE] We want blocking operations to be more responsive to preemption requests.
inline static constexpr std::chrono::milliseconds PREEMPTION_BLOCK_INTERVAL() {
using namespace std::chrono_literals;
return 20ms;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there any downsides of changing the other value from 250ms to 20ms?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I can time some runs on pay-server to see if it impacts throughput.

Copy link
Collaborator Author

Nope! Perf seems unaffected running typecheck on pay-server.

@jvilk-stripe jvilk-stripe force-pushed the jvilk/preempt-globalstate-logic branch from 06c4423 to 60f324b Compare January 14, 2020 21:18
@@ -1491,85 +1491,6 @@ bool GlobalState::wasModified() const {
return wasModified_;
}

bool GlobalState::wasTypecheckingCanceled() const {
Copy link
Collaborator Author
@jvilk-stripe jvilk-stripe Jan 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to TypecheckEpochManager.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome cleanup

@@ -250,21 +239,6 @@ class GlobalState final {
UnorderedMap<NameRef, std::string> dslPlugins;
bool wasModified_ = false;

// In LSP mode: Used to linearize operations involving lastCommittedLSPEpoch.
const std::shared_ptr<absl::Mutex> epochMutex;
Copy link
Collaborator Author
@jvilk-stripe jvilk-stripe Jan 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to TypecheckEpochManager

using namespace std;
namespace sorbet::core::lsp {

void TypecheckEpochManager::assertConsistentThread(optional<thread::id> &expectedThreadId, string_view method,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know all of the thread IDs at construction time, so this assertion primarily asserts that we call specific routines from a consistent thread ID.

if (preemptionManager.has_value()) {
// Now that we are no longer running a slow path, run a preemption task that might have snuck in while we were
// finishing up. No others can be scheduled.
(*preemptionManager)->tryRunScheduledPreemptionTask();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% happy that TypecheckEpochManager knows about PreemptionTaskManager, but I don't have a better solution at the moment.

@@ -915,11 +917,19 @@ ast::ParsedFilesOrCancelled resolve(unique_ptr<core::GlobalState> &gs, vector<as
}

ast::ParsedFilesOrCancelled typecheck(unique_ptr<core::GlobalState> &gs, vector<ast::ParsedFile> what,
const options::Options &opts, WorkerPool &workers) {
const options::Options &opts, WorkerPool &workers, bool cancelable,
optional<shared_ptr<core::lsp::PreemptionTaskManager>> preemptionManager) {
Copy link
Collaborator Author
@jvilk-stripe jvilk-stripe Jan 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could make this non-optional and default to a new preemption manager object (so that preemption will never actually occur), but that would add the overhead of a reader lock acquisition to every file.

@jvilk-stripe
Copy link
Collaborator Author

I believe I've addressed the feedback you've given me, @DarkDimius. I'm working on benchmarking Sorbet w/ the 20ms change.

Other than that, this is ready for review!

// - Worker threads grab as a reader lock, and routinely gives up and re-acquire the lock to allow other requests to
// pre-empt.
// - Typechecking coordinator thread grabs as a writer lock when there's a preemption function, which halts all
// worker threads.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// worker threads.
// worker threads. Only typechecking coordinator can do this and this enforces that multiple threads can't get mutable access to global state.

Copy link
Collaborator
@DarkDimius DarkDimius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the cleanup.
I think it's safe enough now, especially with thread-id checks.

looking forward to rollout!

@jvilk-stripe
Copy link
Collaborator Author
jvilk-stripe commented Jan 15, 2020

The 20ms change has no measurable impact on performance. Typechecking pay-server (on my branch on my devbox, which has some typechecking failures) takes ~same time with and without the change.

@jvilk-stripe jvilk-stripe merged commit 41f7b76 into master Jan 15, 2020
@jvilk-stripe jvilk-stripe deleted the jvilk/preempt-globalstate-logic branch January 15, 2020 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0