-
Notifications
You must be signed in to change notification settings - Fork 14
Replace synchronous file operations with async versions #42
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
base: master
Are you sure you want to change the base?
Conversation
|
I wasn't too sure about best practices for throwing errors, logging to the console, and converting the readFile call, so please let me know what to change! |
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.
Thanks for looking into this! Sorry it took me so long to review. Looks great, except I'd like to convert callbacks to promises and use async/await syntax
| console.log(`${this.logPrefix}Saving code to ${this.codeHostPath}`); | ||
| if (!fs.existsSync(this.dataHostPath)) fs.mkdirSync(this.dataHostPath); | ||
| fs.writeFileSync(this.codeHostPath, code); | ||
| fs.access(this.dataHostPath, fs.constants.F_OK, (errPathExists) => { |
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'd rename errPathExists to something like accessError, since the "PathExists" part could imply that the path does exist
| fs.mkdir(this.dataHostPath, (err) => { if (err) throw err; }); | ||
| } | ||
| }); | ||
| fs.writeFile(this.codeHostPath, code, (err) => { |
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.
The way that Javascript does concurrency can be super confusing at first. We want to do this sequentially:
- Check if the directory exists
- Create it if it doesn't
- Write the file
It might look like this code does that, but actually it does this:
- Check if the directory exists
- Whenever the check completes, create it if it doesn't
- Immediately proceed to write the file
If we could only use callbacks, we would need to put all code following fs.access inside the callback function. This gets ugly pretty quickly. Instead, this codebase uses promises with async/await syntax. The fs.promises module contains a bunch of versions of fs calls that return promises instead of executing callbacks.
We should be able to do something like this:
try {
await fs.promises.mkdir(this.dataHostPath);
} catch (err) {
// Ignore the error if the directory already exists. Otherwise, re-throw it
if (err && err.code != 'EEXIST') {
throw err;
}
}
await fs.promises.writeFile(this.codeHostPath, code);
...
The fs.promises calls return a promise, which is an object that indicates whether the operation has finished (and any returned values or errors from the underlying callback). Then, await says to wait until the promise resolves before executing the subsequent code. (It's actually quite a bit more involved under the hood, but this is the basic idea.)
You'll also need to convert saveCodeFiles to be an async function by adding the async keyword:
private saveCodeFiles = async (code: string, includeFileData: Buffer | null): void => ...
Now that saveCodeFiles is an async function, it returns a promise right away (before it finishes executing) instead of executing synchronously. That means you'll need to add await everywhere it's called.
It looks like it gets called from the Container constructor. Unfortunately constructors cannot be async functions (Javascript requires that objects are constructed synchronously), so you won't be able to use await there. However, since async just makes a function return an ordinary promise, you can do something like this:
this.saveCodeFiles(code, includeFileData)
.then(enableDebugging ? this.initializeDebugSocket : Promise.resolve))
.then(() => this.startContainer(compiler, debugAwareCflags, argsStr, rows, cols, enableDebugging));
Or, since I think we're starting to get several async-y things that we need to do in the constructor, we could define an async function inside of the constructor and invoke that:
// towards end of constructor
(async (): void => {
await this.saveCodeFiles(code, includeFileData);
if (enableDebugging) {
await this.initializeDebugSocket();
}
await this.startContainer(.....);
})().then(); // the .then() is just to suppress a warning about ignoring the promise returned by the async function
| childProcess.execFile('docker', ['kill', this.containerName]); | ||
| this.showErrorBanner('The program exceeded its CPU quota.'); | ||
| } | ||
| fs.readFile( |
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.
Same comments here about using async/await with promises instead of callbacks
Fixes #27