Skip to content

Commit

Permalink
Merge pull request #512 from usethesource/error-recovery/rascal-simpl…
Browse files Browse the repository at this point in the history
…er-debouncer

Simplify the debouncer
  • Loading branch information
sungshik authored Nov 11, 2024
2 parents 9653e26 + 87e5959 commit 5a123b2
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 188 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.Position;
Expand All @@ -51,7 +51,7 @@
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.util.Diagnostics;
import org.rascalmpl.vscode.lsp.util.Versioned;
import org.rascalmpl.vscode.lsp.util.concurrent.Debouncer;
import org.rascalmpl.vscode.lsp.util.concurrent.DebouncedSupplier;
import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps;

import io.usethesource.vallang.IList;
Expand All @@ -76,7 +76,7 @@ public class TextDocumentState {

@SuppressWarnings("java:S3077") // Visibility of writes is enough
private volatile Update current;
private final Debouncer<Update> currentAsyncParseDebouncer;
private final DebouncedSupplier<Update> parseAndGetCurrentDebouncer;

private final AtomicReference<@MonotonicNonNull Versioned<ITree>> lastWithoutErrors;
private final AtomicReference<@MonotonicNonNull Versioned<ITree>> last;
Expand All @@ -91,10 +91,7 @@ public TextDocumentState(
this.columns = columns;

this.current = new Update(initialVersion, initialContent);
this.currentAsyncParseDebouncer = new Debouncer<>(50,
this::getCurrentAsyncIfParsing,
this::parseAndGetCurrentAsync);

this.parseAndGetCurrentDebouncer = new DebouncedSupplier<>(this::parseAndGetCurrent);
this.lastWithoutErrors = new AtomicReference<>();
this.last = new AtomicReference<>();
}
Expand All @@ -114,23 +111,21 @@ public Versioned<String> getCurrentContent() {
}

public CompletableFuture<Versioned<ITree>> getCurrentTreeAsync() {
return current.getTreeAsync(); // Triggers the parser
return getCurrentTreeAsync(Duration.ZERO);
}

public CompletableFuture<Versioned<ITree>> getCurrentTreeAsync(Duration delay) {
return currentAsyncParseDebouncer
.get(delay)
return parseAndGetCurrent(delay)
.thenApply(Update::getTreeAsync)
.thenCompose(Function.identity());
}

public CompletableFuture<Versioned<List<Diagnostic>>> getCurrentDiagnosticsAsync() {
return current.getDiagnosticsAsync(); // Triggers the parser
return getCurrentDiagnosticsAsync(Duration.ZERO);
}

public CompletableFuture<Versioned<List<Diagnostic>>> getCurrentDiagnosticsAsync(Duration delay) {
return currentAsyncParseDebouncer
.get(delay)
return parseAndGetCurrent(delay)
.thenApply(Update::getDiagnosticsAsync)
.thenCompose(Function.identity());
}
Expand All @@ -143,15 +138,19 @@ public CompletableFuture<Versioned<List<Diagnostic>>> getCurrentDiagnosticsAsync
return lastWithoutErrors.get();
}

private @Nullable CompletableFuture<Update> getCurrentAsyncIfParsing() {
private CompletableFuture<Update> parseAndGetCurrent() {
var update = current;
return update.isParsing() ? CompletableFuture.completedFuture(update) : null;
update.parseIfNotParsing();
return CompletableFuture.completedFuture(update);
}

private CompletableFuture<Update> parseAndGetCurrentAsync() {
private CompletableFuture<Update> parseAndGetCurrent(Duration delay) {
var update = current;
update.parseIfNotParsing();
return CompletableFuture.completedFuture(update);
if (update.isParsing()) {
return CompletableFuture.completedFuture(update);
} else {
return parseAndGetCurrentDebouncer.get(delay);
}
}

private class Update {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2018-2023, NWO-I CWI and Swat.engineering
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.rascalmpl.vscode.lsp.util.concurrent;

import java.util.concurrent.atomic.AtomicReference;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

import org.checkerframework.checker.nullness.qual.Nullable;

public class DebouncedSupplier<T> {
private final Supplier<CompletableFuture<T>> supplier;
private final AtomicReference<CompletableFuture<@Nullable CompletableFuture<T>>> latest;

public DebouncedSupplier(Supplier<CompletableFuture<T>> supplier) {
this.supplier = supplier;
this.latest = new AtomicReference<>(CompletableFuture.completedFuture(null));
}

/**
* Gets the result of `supplier` immediately. Previous uncompleted gets are
* completed.
*/
public CompletableFuture<T> get() {
return get(Duration.ZERO);
}

/**
* Gets the result of `supplier` with the given debouncing delay.
*/
public CompletableFuture<T> get(Duration delay) {
return get(delay.toMillis(), TimeUnit.MILLISECONDS);
}

/**
* Gets the result of `supplier` with the given timeout, as follows:
* - if the next get *doesn't* happen before the given timeout, then the
* current get completes with the result of `supplier` upon timeout;
* - if the next get *does* happen before the given timeout, then the
* current get completes with the result of the next get upon
* availability.
*/
public CompletableFuture<T> get(long timeout, TimeUnit unit) {
var newLatest = new CompletableFuture<CompletableFuture<T>>();
var oldLatest = latest.getAndSet(newLatest);

var newLatestResult = newLatest
.thenApply(result -> result == null ? supplier.get() : result)
.thenCompose(Function.identity());

// Complete the current get with the result of `supplier` upon timeout.
// Complete the previous get with the result of the current get upon
// availability.
newLatest.completeOnTimeout(null, timeout, unit);
oldLatest.complete(newLatestResult);

return newLatestResult;
}
}

This file was deleted.

Loading

0 comments on commit 5a123b2

Please sign in to comment.