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

chore: introduce flix.allJobsFinished #395

Merged
merged 9 commits into from
Apr 14, 2024
1 change: 1 addition & 0 deletions client/src/engine/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum Request {
internalMessage = 'ext/message', // Internal Extension Request
internalError = 'ext/error', // Internal Extension Request
internalFinishedJob = 'ext/finished', // Internal Extension Request
internalFinishedAllJobs = 'ext/finishedAll', // Internal Extension Request
internalDiagnostics = 'ext/diagnostics', // Internal Extension Request
internalReplaceConfiguration = 'ext/replaceConfiguration', // Internal Extension Request
}
14 changes: 12 additions & 2 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ export async function activate(context: vscode.ExtensionContext, launchOptions:
registerCommand('flix.showAst', handlers.showAst(client))
registerCommand('flix.startRepl', handlers.startRepl(context, launchOptions))

// Register commands for testing

// Returns a promise resolving when all jobs are completely finished and the server is idle.
// While most other commands can be awaited directly, this is useful for stuff like file creation, which indirectely triggers an asynchronous job.
registerCommand('flix.allJobsFinished', handlers.allJobsFinished(client, eventEmitter))

// watch for changes on the file system (delete, create, rename .flix files)
flixWatcher = vscode.workspace.createFileSystemWatcher(FLIX_GLOB_PATTERN)
flixWatcher.onDidDelete((vsCodeUri: vscode.Uri) => {
Expand Down Expand Up @@ -246,19 +252,23 @@ async function startSession(
})

// Handle when server has answered back after getting the notification above
client.onNotification(jobs.Request.internalReady, function handler() {
client.onNotification(jobs.Request.internalReady, () => {
// waits for server to answer back after having started successfully
eventEmitter.emit(jobs.Request.internalReady)

// start the Flix runner (but only after the Flix LSP instance has started.)
handlers.initSharedRepl(context, launchOptions)
})

client.onNotification(jobs.Request.internalFinishedJob, function handler() {
client.onNotification(jobs.Request.internalFinishedJob, () => {
// only one job runs at once, so currently not trying to distinguish
eventEmitter.emit(jobs.Request.internalFinishedJob)
})

client.onNotification(jobs.Request.internalFinishedAllJobs, () =>
eventEmitter.emit(jobs.Request.internalFinishedAllJobs),
)

client.onNotification(jobs.Request.internalDiagnostics, handlePrintDiagnostics)

client.onNotification(jobs.Request.internalRestart, makeHandleRestartClient(context))
Expand Down
9 changes: 9 additions & 0 deletions client/src/handlers/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode'
import { LanguageClient } from 'vscode-languageclient/node'
import { EventEmitter } from 'events'

import * as jobs from '../engine/jobs'
import ensureFlixExists from './../util/ensureFlixExists'
Expand Down Expand Up @@ -376,3 +377,11 @@ export function startRepl(context: vscode.ExtensionContext, launchOptions: Launc
flixTerminal?.show()
}
}

export function allJobsFinished(client: LanguageClient, eventEmitter: EventEmitter) {
return () =>
new Promise(resolve => {
client.sendNotification(jobs.Request.internalFinishedAllJobs)
eventEmitter.once(jobs.Request.internalFinishedAllJobs, resolve)
})
}
4 changes: 4 additions & 0 deletions server/src/engine/flix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,7 @@ export function enqueueJobWithFlattenedParams(request: jobs.Request, params?: an
}
return queue.enqueue(job)
}

export function unfinishedJobs() {
return queue.unfinishedJobs()
}
1 change: 1 addition & 0 deletions server/src/engine/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum Request {
internalMessage = 'ext/message', // Internal Extension Request
internalError = 'ext/error', // Internal Extension Request
internalFinishedJob = 'ext/finished', // Internal Extension Request
internalFinishedAllJobs = 'ext/finishedAll', // Internal Extension Request
internalDiagnostics = 'ext/diagnostics', // Internal Extension Request
internalReplaceConfiguration = 'ext/replaceConfiguration', // Internal Extension Request
}
Expand Down
7 changes: 7 additions & 0 deletions server/src/engine/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,10 @@ export async function terminateQueue() {
})
emptyQueue()
}

/**
* The number of jobs which have been added to the queue, but have yet to be processed.
*/
export function unfinishedJobs() {
return priorityQueue.length + taskQueue.length + socket.unprocessedRequests()
}
9 changes: 9 additions & 0 deletions server/src/engine/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,13 @@ function handleResponse(flixResponse: FlixResponse, job: jobs.EnqueuedJob) {
clearTimer(flixResponse.id)
// ask queue to process next item
setTimeout(queue.processQueue, 0)

eventEmitter.emit('any')
}

/**
* The number of sent requests which have not yet received a response.
*/
export function unprocessedRequests() {
return Object.keys(sentMessagesMap).length
}
17 changes: 17 additions & 0 deletions server/src/handlers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ export const handleShowAst = enqueueUnlessHasErrors(
hasErrorsHandlerForCommands,
)

/**
* Request a response to be sent when all jobs are finished.
*/
export function handleFinishedAllJobs() {
if (engine.unfinishedJobs() === 0) {
// If already idle, send notification immediately
sendNotification(jobs.Request.internalFinishedAllJobs)
} else {
socket.eventEmitter.on('any', function handler() {
if (engine.unfinishedJobs() === 0) {
sendNotification(jobs.Request.internalFinishedAllJobs)
socket.eventEmitter.removeListener('any', handler)
}
})
}
}

function makeShowAstJob(params: any) {
return {
request: jobs.Request.lspShowAst,
Expand Down
2 changes: 2 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ connection.onNotification(jobs.Request.apiRemJar, handlers.handleRemJar)
// Show ast
connection.onNotification(jobs.Request.lspShowAst, handlers.handleShowAst)

connection.onNotification(jobs.Request.internalFinishedAllJobs, handlers.handleFinishedAllJobs)

// Cleanup after exit
connection.onExit(handlers.handleExit)

Expand Down
20 changes: 17 additions & 3 deletions test/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,34 @@ export async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}

/**
* Waits for the processing of a newly added or deleted file to finish.
*/
async function processFileChange() {
// Wait for the file system watcher to pick up the change
await sleep(1000)

// Wait for the compiler to process the change
await vscode.commands.executeCommand('flix.allJobsFinished')

// Wait for the diagnostics to be updated
await sleep(1000)
}

/**
* Add a file with the given `uri` and `content`, and wait for the compiler to process this.
*/
export async function addFile(uri: vscode.Uri, content: string) {
await vscode.workspace.fs.writeFile(uri, Buffer.from(content))
await sleep(6000)
await processFileChange()
}

/**
* Copy the file from `from` to `to`, and wait for the compiler to process this.
*/
export async function copyFile(from: vscode.Uri, to: vscode.Uri) {
await vscode.workspace.fs.copy(from, to)
await sleep(6000)
await processFileChange()
}

/**
Expand All @@ -77,5 +91,5 @@ export async function copyFile(from: vscode.Uri, to: vscode.Uri) {
*/
export async function deleteFile(uri: vscode.Uri) {
await vscode.workspace.fs.delete(uri)
await sleep(2000)
await processFileChange()
}
Loading