-
Notifications
You must be signed in to change notification settings - Fork 248
Implement idle-timeout shutdown. #1395
Conversation
sources/web/datalab/idleTimeout.ts
Outdated
|
||
const idleCheckIntervalSeconds = 5; // Seconds to wait between idle checks | ||
|
||
let idleTimeoutEnabled = true; // Allow for explicit disable without clearing other values |
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'm not sure I understand this comment. maybe clarify a bit?
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 changed the comment on this line and the next. Let me know if this is any better.
sources/web/datalab/idleTimeout.ts
Outdated
|
||
let idleTimeoutEnabled = true; // Allow for explicit disable without clearing other values | ||
let idleTimeoutSeconds = 0; // Shutdown after being idle this long; disabled if 0 or NaN | ||
let lastUserActivity: number; // The epoch, in seconds, of the last user activity |
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.
also, can we define what user activity means here? or ideally in a comment at the head of the file
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 renamed this variable to be more reflective of reality. This module doesn't define what user activity is.
export VM_PROJECT="${vm_project}" | ||
export VM_NAME=$(curl -s "${compute_metadata_url}/instance/hostname" -H "Metadata-Flavor: Google" | cut -d '.' -f 1) | ||
export VM_ZONE=$(curl -s "${compute_metadata_url}/instance/zone" -H "Metadata-Flavor: Google" | sed 's/.*zones\///') | ||
export DATALAB_SHUTDOWN_COMMAND="gcloud compute instances stop ${VM_NAME} --project ${VM_PROJECT} --zone ${VM_ZONE}" |
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.
FYI, we can get all three values by importing info
.
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.
You mean in the javascript code?
Putting the shutdown command here means it gets set when running in a VM but not otherwise.
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.
but what's the value of this? we can already check if we're in a VM.
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.
By using this environment variable, it is easy for me to set a different shutdown command in my startup.sh that shuts down my local notebook server during development. If you prefer, I can remove this line from this script, and instead add code to idleTimeout.ts that calls getVmInfo, checks to see if we are on a VM, and builds this shutdown command if so.
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 see, no I think it's fine to stick with the env var if it's more convenient in code.
sources/web/datalab/idleTimeout.ts
Outdated
} | ||
} else { | ||
idleTimeoutSeconds = NaN; // No shutdown command available | ||
logging.getLogger().debug('No shutdown command, idle timeout is disabled'); |
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 one should probably be info
instead of debug
, this is the default log level.
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.
Done.
|
||
// Parse a string like '30s' or '1h 15m 5s' and return seconds. | ||
// If no input string, or unrecognized stuff, returns NaN. | ||
function parseInterval(str: string) { |
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.
should we grab an npm package for this? something like https://www.npmjs.com/package/parse-duration.
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.
Sounds reasonable. What are the rules regarding adding new packages to what we ship? Licensing guidelines?
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.
MIT license is generally fine.
@Di-Ku just in case.
let showDebugLog = false; // Set to true in the dev console to get log messages during development. | ||
function _debugLog(msg) { | ||
if (showDebugLog) { | ||
console.log(msg); |
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.
can we import and use util.debug.log
instead?
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.
Done.
window.setTimeout(_alertIdleShutdown, delayToLetOtherHandlersRun); | ||
} | ||
} | ||
timeoutInfo.enabled = false; // Make sure we don't get a false indication 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.
should this go inside the inner if statement?
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.
No, I want it to be disabled any time this method is called. Changed the method name to be clear about that.
_debugLog('== updating timeout display'); | ||
_debugLog(dropdown); | ||
if (timeoutInfo.enabled) { | ||
let secondsRemaining = Math.floor((timeoutInfo.expirationTime - Date.now()) / 1000); |
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 really don't think this should be in seconds, because of the fact that the client code isn't always in sync with the server state. the user will likely see the timer go down second by second, then jump up when the server resets and the client polls the new state.
minutes makes a lot more sense to me.
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.
A few lines below, the call to roundToApproximateTime will generally round to minutes or larger chunks. The user will only see seconds when the time gets down to the last couple of minutes. I find seeing the seconds to be useful for development work. I expect users will rarely see that.
_debugLog('== Changing enabled to ' + newValue); | ||
updateTimeoutInfo(dropdown); | ||
xhr(timeoutUrl, () => { | ||
timeoutInfo.enabled = newValue; // Display the new value right away. |
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 also put the UI update here in the success callback
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.
It is there a couple of lines below. Or did you mean something besides a call to updateTimeoutInfo?
}); | ||
|
||
idleTimeout.setupKernelBusyHeartbeat(); | ||
$('#mainArea').scroll(idleTimeout.notebookScrolled); |
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 think we'll need a different div to trigger this event in the non-notebook pages
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 haven't played around as much with non-notebook pages. The list page is very reset-happy, it seems to refresh the list of notebooks pretty often, so I didn't bother adding anything else there.
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.
Yea I think you're right about the list page, the sessions page also uses the same code so it'll do the same refresh requests.
We have to think about other pages though, for now we only have the editor and terminal pages, which both sound like the user can spend some time on. Can we make sure we have those covered?
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 will take a look at those pages.
export VM_PROJECT="${vm_project}" | ||
export VM_NAME=$(curl -s "${compute_metadata_url}/instance/hostname" -H "Metadata-Flavor: Google" | cut -d '.' -f 1) | ||
export VM_ZONE=$(curl -s "${compute_metadata_url}/instance/zone" -H "Metadata-Flavor: Google" | sed 's/.*zones\///') | ||
export DATALAB_SHUTDOWN_COMMAND="gcloud compute instances stop ${VM_NAME} --project ${VM_PROJECT} --zone ${VM_ZONE}" |
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.
but what's the value of this? we can already check if we're in a VM.
return total; | ||
} | ||
|
||
export function resetBasedOnPath(path: string) { |
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.
Never mind, I missed this was actually called before handling requests.
}); | ||
|
||
idleTimeout.setupKernelBusyHeartbeat(); | ||
$('#mainArea').scroll(idleTimeout.notebookScrolled); |
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.
Yea I think you're right about the list page, the sessions page also uses the same code so it'll do the same refresh requests.
We have to think about other pages though, for now we only have the editor and terminal pages, which both sound like the user can spend some time on. Can we make sure we have those covered?
it('converts single-unit strings correctly', function() { | ||
expect(idleTimeout._secondsToString(1)).toEqual('1s'); | ||
expect(idleTimeout._secondsToString(15)).toEqual('15s'); | ||
expect(idleTimeout._secondsToString(2*60)).toEqual('2m'); |
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.
one more test with an unrounded number, e.g. 260+1 or 360*60+61?
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.
Multi-unit outputs are in the next test, a few lines lower. Is there something you want me to add there?
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.
Nope, I think it's all covered.
No description provided.