-
Notifications
You must be signed in to change notification settings - Fork 394
Run git commands in dedicated renderer process #688
Conversation
Only need one RendererProcessManager for each Atom window. Also reset RendererProcessManager in afterEach.
Since we're using a renderer process for spawning git processes right now
... to go along with hostWebContentsId
The sourceWebContentsId is now encoded in the message data
…u-renderer-shell-out
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.
This is really, really cool. I'll install this branch and give it a shot over the next few days but I'd ❤️ to get this merged and out to our staff users as soon as we can 😁
}); | ||
}, {parallel: !writeOperation}); | ||
/* eslint-enable no-console */ | ||
} | ||
|
||
executeGitCommand(args, options, marker = null) { | ||
if (process.env.ATOM_GITHUB_INLINE_GIT_EXEC || !WorkerManager.getInstance().isReady()) { |
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.
👍 for offering an env var to opt out of the workers for when someone runs this on their Raspberry Pi 😉
If I'm reading it right it'll prevent the first worker from spawning too?
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.
If I'm reading it right it'll prevent the first worker from spawning too?
Yup 👍 . Second expression after ||
doesn't get evaluated if first expression evaluates to true
@@ -220,6 +239,7 @@ export default class GitShellOutStrategy { | |||
|
|||
async isGitRepository() { | |||
try { | |||
await fsStat(this.workingDir); // fails if folder doesn't exist |
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.
Cool, cool. This fixes that uncaught-exception-in-Promise from the test suite I presume.
lib/renderer.html
Outdated
<body> | ||
<h1>GitHub Package Git Execution Window</h1> | ||
<p> | ||
Hi there! I'm a window used by the GitHub pacakge to execute Git commands in the background. My PID is <script>document.write(process.pid)</script>. |
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.
✍️ "GitHub pacakge"
(... Nobody will ever see this 😛 )
static status = { | ||
PENDING: Symbol('pending'), | ||
INPROGRESS: Symbol('in-progress'), | ||
COMPLETE: Symbol('complete'), |
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.
Hooray for Symbols 🎉
this.startTime = performance.now(); | ||
return execFn({...this.data, id: this.id}); | ||
} | ||
} |
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.
This WorkerManager/Operation setup is downright elegant 😄
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.
🙇♀️
// For now we won't do this to avoid clogging up ipc channel | ||
// event.sender.sendTo(managerWebContentsId, channelName, {sourceWebContentsId, type: 'exec-started', data: {id}}); | ||
|
||
if (averageTracker.enoughData() && averageTracker.getAverage() > 20) { |
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.
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 yo
@@ -158,6 +164,16 @@ export function createRenderer() { | |||
return renderer; | |||
} | |||
|
|||
export function isProcessAlive(pid) { |
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'll be honest: before I clicked that link, I really expected "Stayin' Alive." Should have known better 😆
test/worker-manager.test.js
Outdated
workerManager.request(); | ||
workerManager.request(); | ||
const worker1OperationsInFlight = worker1.getRemainingOperations(); | ||
assert.equal(worker1OperationsInFlight.length, 3); |
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.
There's an assert.lengthOf(...)
too... I think it shows you the array contents if the length is wrong but it isn't too long?
test/worker-manager.test.js
Outdated
assert.equal(worker1OperationsInFlight.length, 3); | ||
|
||
const worker1Pid = worker1.getPid(); | ||
process.kill(worker1Pid, 'SIGKILL'); |
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.
🔪
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.
Haha, to clarify, this was my attempt at a SIGKILL
emoji, not an indication that this line shouldn't be here or anything.
const headerStyle = 'font-weight: bold; color: blue;'; | ||
|
||
console.groupCollapsed(`git:${formattedArgs}`); | ||
console.log('%cexit status%c %d', headerStyle, 'font-weight: normal; color: black;', exitCode); |
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 day I discovered how %c
works in console.log()
was a dangerous day.
Fixes #386.
Fixes #643.
This PR addresses the issue of Atom freezing due to multiple blocking spawn calls when shelling out to Git in process (#386).
The Options
The solution is to make Git calls in a separate process, options being a Node child process, Electron renderer process, or Chromium web worker.
This PR implements a dedicated renderer process for shelling out to Git.
Basic testing with a Node child process revealed quadratic growth in IPC time relative to message size so this approach was dismissed due to the negative performance implications. The fix for this won't be released until Node v7.5.0.
Web workers are the ideal solution, but will have to wait until Atom is upgraded to Electron v1.6.4 which adds Node integration to web workers.
Current Approach
This PR introduces a
WorkerManager
which creates aWorker
that wraps aRendererProcess
which shells out to Git and sends results back over IPC. If the renderer process is not yet ready, we fall back to shelling out in process.The renderer process keeps a running average of the time it takes to make a spawn call and if this exceeds 20ms (and the minimum number of operations specified have been completed), the
WorkerManager
creates a newRendererProcess
and routes all new Git data requests to it. Once the "sick" renderer process finishes its operations it will be destroyed. The new renderer process may have a higheroperationCountLimit
, so that if the spawn calls continue to take longer than 20ms, we'll wait longer before creating a newRendererProcess
in order to prevent process thrashing on slow machines.If a renderer process crashes, the
WorkerManager
will create a newRendererProcess
with the sameoperationCountLimit
and send all incomplete operations to the new process.If the
WorkerManager
process is destroyed, allRendererProcess
instances will immediately destroy themselves.If a
Worker
has it'sdestroy
method called and there are remaining operations to be completed, we wait until completion before destroying the associatedRendererProcess
. This is in the event that there is a write operation that is still in progress and we want to allow it to reach disk.Things to consider for future PRs:
Operation
as "in-progress" by sending an ipc message afterGitProcess.exec
has been called.