Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

improvements to file system events processing for search #12885

Merged
merged 4 commits into from
Nov 17, 2016

Conversation

zaggino
Copy link
Contributor

@zaggino zaggino commented Nov 9, 2016

Hi guys, I've reported elsewhere that my db dump script crashes both brackets (white-screen) and also brackets-electron. This is the work done to fix that (and I can confirm that with this fix there's no crash when running my script anymore) - it's not final and I'd like to have it reviewed before working on little things as docs and similar.

There're two main things, first aggregate duplicate events and debounce for a short while before processing. My script quickly deletes and creates back a lot of stuff, before this change some of the directories were removed and added back to search multiple times. With debouncing it's only once after script is done.

Second, no less important thing is fixing calls to filesChanged and filesRemoved functions. In case of my script these two were called about 1000 times with an array of size 1 which cause a lot of trouble for IPC connection in electron and socket connection in regular brackets. With the fix these are now properly called.

Marking this as high-priority because it fixes brackets completely crashing in some cases.

Copy link
Collaborator

@petetnt petetnt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments.

@@ -657,8 +665,8 @@ define(function (require, exports, module) {
* Inform node that the list of files has changed.
* @param {array} fileList The list of files that changed.
*/
function filesChanged(fileList) {
if (FindUtils.isNodeSearchDisabled() || fileList.length === 0) {
function filesChanged(fileList) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some trailing whitespace here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

function filesChanged(fileList) {
if (FindUtils.isNodeSearchDisabled() || fileList.length === 0) {
function filesChanged(fileList) {
if (FindUtils.isNodeSearchDisabled() || !fileList || fileList.length === 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!fileList covers fileList.length === 0 too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it doesn't

var emptyArray = []; var a = !emptyArray; var b = emptyArray.length === 0; [a,b];
[false, true]

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, sorry about that @zaggino, I automatically assumed that fileList would never be an empty array just undefined. Again, sorry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving it as is, it's a very cheap check and better be safe than sorry :)

// node search : inform node that the file is removed
fullPaths.push(fullPath);
if (findOrReplaceInProgress) {
searchModel.removeResults(fullPath);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called once with a large array too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeResults is a classic sync method, so it wouldn't do any benefit here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zaggino@ Bro how are you today? Please Bro help us we want CoffeeScript in Brakets and also
Babel , and many's other please

resultsChanged = true;
function _removeSearchResultsForEntries(entries) {
var fullPaths = [];
entries.forEach(function (entry) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth checking out if couple of Array.prototype.maps would be faster than nested forEachs and a push

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fullPaths.push(fullPath); is inside an if so if map would be used, we'd need to filter it out for nulls afterwards

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point 👍

@petetnt
Copy link
Collaborator

petetnt commented Nov 9, 2016

Should avoid doing code reviews when I am tired 😴 👍

Nice work @zaggino

clearSearch;

/* Waits for FS changes to stack up until processing them (scripts like npm install can do a lot of movements on the disk) */
var FILE_SYSTEM_EVENT_DEBOUNCE_TIME = 1000;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be something like 100ms instead? Or is too low?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right here, re-tested this set to 100ms with my script and it's fine. So setting it to 100ms.

addedFiles.push(child);
addedFilePaths.push(child.fullPath);
function _addSearchResultsForEntries(entries) {
var fullPaths = [];
Copy link
Collaborator

@ficristo ficristo Nov 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a rough idea of the memory usage here? If we store a lot of path could cause some trouble?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this array is not being persisted besides the function execution lifetime and I wouldn't expect there to be a problem even if you have thousands of entries in it. On the other hand, without it, trying to send thousands of messages to a node process will definitely crash it (or socket server). In other words, I don't think memory usage is something to worry about.

}, FILE_SYSTEM_EVENT_DEBOUNCE_TIME);

_debouncedFileSystemChangeHandler = function (event, entry, added, removed) {
_cachedFileSystemEvents.push([ event, entry, added, removed ]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you push an object instead of an array? So I will not feel so dumb when reading the reduce function above...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, can do.

@ficristo
Copy link
Collaborator

Nice work @zaggino

👍

@zaggino
Copy link
Contributor Author

zaggino commented Nov 14, 2016

Pushed some changes, @petetnt @ficristo please review again :-)

_.forEach(_cachedFileSystemEvents, function (obj) {
_fileSystemChangeHandler(obj.event, obj.entry, obj.added, obj.removed);
});
_cachedFileSystemEvents = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are accessing the same global, and this function is called with a delay, don't we risk here to delete the events that have been added in the bouncedFileSystemChangeHandler function while waiting this to run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly? There's a _.forEach above this that launches _fileSystemChangeHandler for every object in the array, before deleting it. The function is completely synchronous - if an fs event is emitted while this is running, it'll be added to the empty array once this function finishes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably I was overthinking, but I was wondering if there could be some races between _debouncedFileSystemChangeHandler and _processCachedFileSystemEvents.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, races certainly can't happen in this case. They are both completely sync functions - no promises or async-await there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debounce and relative timout are threwing me off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debounce just wraps a function and keeps postponing its execution until the wrapper stops to be called for the timeout time. But once the wrapped function is executed, all happens totally sync so the code is safe. I've used this pattern a few times before.

_processCachedFileSystemEvents = _.debounce(function () {
// we need to reduce _cachedFileSystemEvents not to contain duplicates!
_cachedFileSystemEvents = _cachedFileSystemEvents.reduce(function (result, obj) {
var fullPath = obj.entry ? obj.entry.fullPath : null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If fullPath is null maybe return immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deeper in the code there's a handle for this case (doing a full refresh I think) so it didn't want to stop it here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the assignment below is safe: result[fullPath] = obj; ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it actuall assigns result[null] = obj; which is a bit weird but valid so I left it like that.

@zaggino zaggino added this to the Release 1.9 milestone Nov 15, 2016
@zaggino
Copy link
Contributor Author

zaggino commented Nov 17, 2016

@petetnt anything else? if not it should be good to merge

@petetnt
Copy link
Collaborator

petetnt commented Nov 17, 2016

@zaggino LGTM, I'll run this in production use for today and merge it in if nothing pops up.

Copy link
Collaborator

@swmitra swmitra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@petetnt petetnt merged commit 94ed8b3 into master Nov 17, 2016
@petetnt
Copy link
Collaborator

petetnt commented Nov 17, 2016

Used it all day without any issues and Brackets did feel snappier. Great job once again @zaggino!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants