-
Notifications
You must be signed in to change notification settings - Fork 323
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
Initialize suggestions database only once #8116
Merged
mergify
merged 14 commits into
develop
from
wip/db/8033-initialize-suggestions-database-only-once
Oct 21, 2023
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
83bbf0e
DRAFT: isInitialized
4e6 af45b90
fix: language server tests
4e6 0ab1391
refactor: unique jobs
4e6 5785a74
feat: unique background jobs
4e6 b5ff12b
fix: getSuggestionsDatabase
4e6 8656959
refactor: initialization components
4e6 c0e019b
fix: background processing
4e6 a79880d
fix: language server tests
4e6 62bd1eb
misc: cleanup
4e6 148944f
misc: javafmt
4e6 50be800
Merge branch 'develop' into wip/db/8033-initialize-suggestions-databa…
4e6 cae2087
misc: document initialization components
4e6 117aaf2
misc: fix runtime tests
4e6 2799aa3
misc: review comments
4e6 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
37 changes: 37 additions & 0 deletions
37
...ver/src/main/java/org/enso/languageserver/boot/resource/AsyncResourcesInitialization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
import java.util.Arrays; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
/** Component that initializes resources in parallel. */ | ||
public class AsyncResourcesInitialization implements InitializationComponent { | ||
|
||
private final InitializationComponent[] resources; | ||
|
||
/** | ||
* Create async initialization component. | ||
* | ||
* @param resources the list of resources to initialize | ||
*/ | ||
public AsyncResourcesInitialization(InitializationComponent... resources) { | ||
this.resources = resources; | ||
} | ||
|
||
@Override | ||
public boolean isInitialized() { | ||
return Arrays.stream(resources).allMatch(InitializationComponent::isInitialized); | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Void> init() { | ||
return CompletableFuture.allOf( | ||
Arrays.stream(resources) | ||
.map( | ||
component -> | ||
component.isInitialized() | ||
? CompletableFuture.completedFuture(null) | ||
: component.init()) | ||
.toArray(CompletableFuture<?>[]::new)) | ||
.thenRun(() -> {}); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
...ge-server/src/main/java/org/enso/languageserver/boot/resource/BlockingInitialization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.Semaphore; | ||
|
||
/** Initialization component ensuring that only one initialization sequence is running at a time. */ | ||
public final class BlockingInitialization implements InitializationComponent { | ||
|
||
private final InitializationComponent component; | ||
private final Semaphore lock = new Semaphore(1); | ||
|
||
/** | ||
* Create blocking initialization component. | ||
* | ||
* @param component the underlying initialization component to run | ||
*/ | ||
public BlockingInitialization(InitializationComponent component) { | ||
this.component = component; | ||
} | ||
|
||
@Override | ||
public boolean isInitialized() { | ||
return component.isInitialized(); | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Void> init() { | ||
try { | ||
lock.acquire(); | ||
} catch (InterruptedException e) { | ||
return CompletableFuture.failedFuture(e); | ||
} | ||
return component.init().whenComplete((res, err) -> lock.release()); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...server/src/main/java/org/enso/languageserver/boot/resource/DirectoriesInitialization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.Executor; | ||
import org.enso.languageserver.data.ProjectDirectoriesConfig; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** Directories initialization. */ | ||
public class DirectoriesInitialization implements InitializationComponent { | ||
|
||
private final Executor executor; | ||
private final ProjectDirectoriesConfig projectDirectoriesConfig; | ||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||
|
||
private volatile boolean isInitialized = false; | ||
|
||
/** | ||
* Creates the directories initialization component. | ||
* | ||
* @param executor the executor that runs the initialization | ||
* @param projectDirectoriesConfig the directories config | ||
*/ | ||
public DirectoriesInitialization( | ||
Executor executor, ProjectDirectoriesConfig projectDirectoriesConfig) { | ||
this.executor = executor; | ||
this.projectDirectoriesConfig = projectDirectoriesConfig; | ||
} | ||
|
||
@Override | ||
public boolean isInitialized() { | ||
return isInitialized; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Void> init() { | ||
return CompletableFuture.runAsync( | ||
() -> { | ||
logger.info("Initializing directories..."); | ||
projectDirectoriesConfig.createDirectories(); | ||
logger.info("Initialized directories."); | ||
isInitialized = true; | ||
}, | ||
executor); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...e-server/src/main/java/org/enso/languageserver/boot/resource/InitializationComponent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
|
||
/** A component that should be initialized. */ | ||
public interface InitializationComponent { | ||
|
||
/** @return `true` if the component is initialized */ | ||
boolean isInitialized(); | ||
|
||
/** Initialize the component. */ | ||
CompletableFuture<Void> init(); | ||
} |
19 changes: 19 additions & 0 deletions
19
...c/main/java/org/enso/languageserver/boot/resource/InitializationComponentInitialized.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
/** Object indicating that the initialization is complete. */ | ||
public final class InitializationComponentInitialized { | ||
|
||
private static final class InstanceHolder { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need for |
||
private static final InitializationComponentInitialized INSTANCE = | ||
new InitializationComponentInitialized(); | ||
} | ||
|
||
/** | ||
* Get the initialized marker object. | ||
* | ||
* @return the instance of {@link InitializationComponentInitialized}. | ||
*/ | ||
public static InitializationComponentInitialized getInstance() { | ||
return InstanceHolder.INSTANCE; | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
...age-server/src/main/java/org/enso/languageserver/boot/resource/JsonRpcInitialization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.Executor; | ||
import org.enso.jsonrpc.ProtocolFactory; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** Initialization of JSON-RPC protocol. */ | ||
public class JsonRpcInitialization implements InitializationComponent { | ||
|
||
private final Executor executor; | ||
private final ProtocolFactory protocolFactory; | ||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||
|
||
private volatile boolean isInitialized = false; | ||
|
||
/** | ||
* Create an instance of JSON-RPC initialization component. | ||
* | ||
* @param executor the executor that runs the initialization | ||
* @param protocolFactory the JSON-RPC protocol factory | ||
*/ | ||
public JsonRpcInitialization(Executor executor, ProtocolFactory protocolFactory) { | ||
this.executor = executor; | ||
this.protocolFactory = protocolFactory; | ||
} | ||
|
||
@Override | ||
public boolean isInitialized() { | ||
return isInitialized; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Void> init() { | ||
return CompletableFuture.runAsync( | ||
() -> { | ||
logger.info("Initializing JSON-RPC protocol."); | ||
protocolFactory.init(); | ||
logger.info("JSON-RPC protocol initialized."); | ||
isInitialized = true; | ||
}, | ||
executor); | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
...nguage-server/src/main/java/org/enso/languageserver/boot/resource/RepoInitialization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package org.enso.languageserver.boot.resource; | ||
|
||
import akka.event.EventStream; | ||
import java.io.IOException; | ||
import java.nio.file.FileSystemException; | ||
import java.nio.file.Files; | ||
import java.nio.file.NoSuchFileException; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionException; | ||
import java.util.concurrent.CompletionStage; | ||
import java.util.concurrent.Executor; | ||
import org.apache.commons.io.FileUtils; | ||
import org.enso.languageserver.data.ProjectDirectoriesConfig; | ||
import org.enso.languageserver.event.InitializedEvent; | ||
import org.enso.logger.masking.MaskedPath; | ||
import org.enso.searcher.sql.SqlDatabase; | ||
import org.enso.searcher.sql.SqlSuggestionsRepo; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import scala.jdk.javaapi.FutureConverters; | ||
|
||
/** Initialization of the Language Server suggestions database. */ | ||
public class RepoInitialization implements InitializationComponent { | ||
4e6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private static final int MAX_RETRIES = 3; | ||
private static final long RETRY_DELAY_MILLIS = 1000; | ||
|
||
private final Executor executor; | ||
|
||
private final ProjectDirectoriesConfig projectDirectoriesConfig; | ||
private final EventStream eventStream; | ||
private final SqlDatabase sqlDatabase; | ||
private final SqlSuggestionsRepo sqlSuggestionsRepo; | ||
|
||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||
|
||
private volatile boolean isInitialized = false; | ||
|
||
/** | ||
* Create an instance of repo initialization component. | ||
* | ||
* @param executor the executor that runs the initialization | ||
* @param projectDirectoriesConfig configuration of language server directories | ||
* @param eventStream the events stream | ||
* @param sqlDatabase the sql database | ||
* @param sqlSuggestionsRepo the suggestions repo | ||
*/ | ||
public RepoInitialization( | ||
Executor executor, | ||
ProjectDirectoriesConfig projectDirectoriesConfig, | ||
EventStream eventStream, | ||
SqlDatabase sqlDatabase, | ||
SqlSuggestionsRepo sqlSuggestionsRepo) { | ||
this.executor = executor; | ||
this.projectDirectoriesConfig = projectDirectoriesConfig; | ||
this.eventStream = eventStream; | ||
this.sqlDatabase = sqlDatabase; | ||
this.sqlSuggestionsRepo = sqlSuggestionsRepo; | ||
} | ||
|
||
@Override | ||
public boolean isInitialized() { | ||
return isInitialized; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Void> init() { | ||
return initSqlDatabase() | ||
.thenComposeAsync(v -> initSuggestionsRepo(), executor) | ||
.thenRun(() -> isInitialized = true); | ||
} | ||
|
||
private CompletableFuture<Void> initSqlDatabase() { | ||
return CompletableFuture.runAsync( | ||
() -> { | ||
logger.info("Initializing sql database [{}]...", sqlDatabase); | ||
sqlDatabase.open(); | ||
logger.info("Initialized sql database [{}].", sqlDatabase); | ||
}, | ||
executor) | ||
.whenCompleteAsync( | ||
(res, err) -> { | ||
if (err != null) { | ||
logger.error("Failed to initialize sql database [{}].", sqlDatabase, err); | ||
} | ||
}, | ||
executor); | ||
} | ||
|
||
private CompletableFuture<Void> initSuggestionsRepo() { | ||
return CompletableFuture.runAsync( | ||
() -> logger.info("Initializing suggestions repo [{}]...", sqlDatabase), executor) | ||
.thenComposeAsync( | ||
v -> | ||
doInitSuggestionsRepo().exceptionallyComposeAsync(this::recoverInitializationError), | ||
executor) | ||
.thenRunAsync( | ||
() -> logger.info("Initialized Suggestions repo [{}].", sqlDatabase), executor) | ||
.whenCompleteAsync( | ||
(res, err) -> { | ||
if (err != null) { | ||
logger.error("Failed to initialize SQL suggestions repo [{}].", sqlDatabase, err); | ||
} else { | ||
eventStream.publish(InitializedEvent.SuggestionsRepoInitialized$.MODULE$); | ||
} | ||
}); | ||
} | ||
|
||
private CompletableFuture<Void> recoverInitializationError(Throwable error) { | ||
return CompletableFuture.runAsync( | ||
() -> | ||
logger.warn( | ||
"Failed to initialize the suggestions database [{}].", sqlDatabase, error), | ||
executor) | ||
.thenRunAsync(sqlDatabase::close, executor) | ||
.thenComposeAsync(v -> clearDatabaseFile(0), executor) | ||
.thenRunAsync(sqlDatabase::open, executor) | ||
.thenRunAsync(() -> logger.info("Retrying database initialization."), executor) | ||
.thenComposeAsync(v -> doInitSuggestionsRepo(), executor); | ||
} | ||
|
||
private CompletableFuture<Void> clearDatabaseFile(int retries) { | ||
return CompletableFuture.runAsync( | ||
() -> { | ||
logger.info("Clear database file. Attempt #{}.", retries + 1); | ||
try { | ||
Files.delete(projectDirectoriesConfig.suggestionsDatabaseFile().toPath()); | ||
} catch (IOException e) { | ||
throw new CompletionException(e); | ||
} | ||
}, | ||
executor) | ||
.exceptionallyComposeAsync(error -> recoverClearDatabaseFile(error, retries), executor); | ||
} | ||
|
||
private CompletableFuture<Void> recoverClearDatabaseFile(Throwable error, int retries) { | ||
if (error instanceof CompletionException) { | ||
return recoverClearDatabaseFile(error.getCause(), retries); | ||
} else if (error instanceof NoSuchFileException) { | ||
logger.warn( | ||
"Failed to delete the database file. Attempt #{}. File does not exist [{}].", | ||
retries + 1, | ||
new MaskedPath(projectDirectoriesConfig.suggestionsDatabaseFile().toPath())); | ||
return CompletableFuture.completedFuture(null); | ||
} else if (error instanceof FileSystemException) { | ||
logger.error( | ||
"Failed to delete the database file. Attempt #{}. The file will be removed during the shutdown.", | ||
retries + 1, | ||
error); | ||
Runtime.getRuntime() | ||
.addShutdownHook( | ||
new Thread( | ||
() -> | ||
FileUtils.deleteQuietly(projectDirectoriesConfig.suggestionsDatabaseFile()))); | ||
return CompletableFuture.failedFuture(error); | ||
} else if (error instanceof IOException) { | ||
logger.error("Failed to delete the database file. Attempt #{}.", retries + 1, error); | ||
if (retries < MAX_RETRIES) { | ||
try { | ||
Thread.sleep(RETRY_DELAY_MILLIS); | ||
} catch (InterruptedException e) { | ||
throw new CompletionException(e); | ||
} | ||
return clearDatabaseFile(retries + 1); | ||
} else { | ||
return CompletableFuture.failedFuture(error); | ||
} | ||
} | ||
|
||
return CompletableFuture.completedFuture(null); | ||
} | ||
|
||
private CompletionStage<Void> doInitSuggestionsRepo() { | ||
return FutureConverters.asJava(sqlSuggestionsRepo.init()).thenAccept(res -> {}); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't
init
assume thatisInitialized
isfalse
at the beginning of the execution?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented it in a higher-level initialization components