Skip to content

Conversation

@irfannuri
Copy link
Contributor

@irfannuri irfannuri commented Jun 16, 2017

This PR enables running cypher queries on separate web workers (when available) so that web browser does not freeze and remains responsive. A brief list of facts about the implementation is listed below:

  • There still exists an enduring connection in main thread to perform regular metadata updates and such needs
  • When a cypher query is executed from editor (so routedWriteTransaction is called), a new web worker is created and the request is passed to that worker
  • Each web worker creates a new driver object, session and connection which survive through request lifecycle
  • Web worker and connection related resources are disposed either with the ordinary termination or cancellation of request.

With the use of web workers, it is possible to run multiple long running queries and still use the browser as seen in the attached screen shot. This was not possible, especially when the running query immediately started streaming results, which caused bolt driver to fully occupy cpu thus making UI non-responsive.

To disable web workers, for a limited time you can do so by :config useCypherThread: false.
This option will probably be removed in the future.

screen shot 2017-06-16 at 13 35 44

changelog: Use separate thread for running cypher queries

Copy link
Member

@oskarhane oskarhane left a comment

Choose a reason for hiding this comment

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

Good work!
I added a few questions for things I don't understand.

package.json Outdated
"webpack-dashboard": "^0.4.0",
"webpack-dev-server": "^2.4.2"
"webpack-dev-server": "^2.4.2",
"worker-loader": "^0.8.0"
Copy link
Member

Choose a reason for hiding this comment

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

Indentation

if (enableWebWorkers && window.Worker) {
const id = requestId || v4()
/* eslint-disable import/no-webpack-loader-syntax */
const BoltWorkerModule = require('worker-loader!./boltWorker.js')
Copy link
Member

Choose a reason for hiding this comment

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

Why not import as a regular module at the top of the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

let connectionProperties = null
let boltWorkerRegister = {}
let cancellationRegister = {}
let enableWebWorkers = true
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should start off by have this as a "experimental" setting in the config pane?
wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

screen shot 2017-06-28 at 16 09 43

return rows.map((row) => row.map((entry) => (entry && entry.properties) ? entry.properties : entry))
}

export const applyGraphTypes = (item) => {
Copy link
Member

Choose a reason for hiding this comment

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

I'd like to see this function tested, so we don't introduce regressions if we need to make changes in it in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely. Done!

}
}

self.addEventListener('message', onmessage)
Copy link
Member

Choose a reason for hiding this comment

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

What is self?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is needed by worker loader on the worker script


export const applyGraphTypes = (item) => {
if (Array.isArray(item)) {
return item.map(arrayItem => applyGraphTypes(arrayItem))
Copy link
Member

Choose a reason for hiding this comment

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

Minor thing, but it's easier to read if you do this item.map(applyGraphTypes).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

} else if (item.hasOwnProperty('segments') && item.hasOwnProperty('start') && item.hasOwnProperty('end')) {
const start = applyGraphTypes(item.start)
const end = applyGraphTypes(item.end)
const segments = item.segments.map(segment => applyGraphTypes(segment))
Copy link
Member

Choose a reason for hiding this comment

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

Same as above: item.map(applyGraphTypes).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

test('should apply node type', () => {
const rawNode = {
labels: ['Test'],
properties: [],
Copy link
Member

Choose a reason for hiding this comment

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

Please add some properties of different types (integers, string, array, null, object) to make sure they come along.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return new neo4j.types.Relationship(neo4j.int(item.identity), neo4j.int(item.start), neo4j.int(item.end), item.type, applyGraphTypes(item.properties))
} else if (item.hasOwnProperty('low') && item.hasOwnProperty('high') && typeof item.low === 'number' && typeof item.high === 'number') {
return neo4j.int(item)
} else if (typeof item === 'object') {
Copy link
Member

Choose a reason for hiding this comment

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

null is of type object so this will probably throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

definitely! done.

expect(typedRelationships[1].end instanceof neo4j.Integer).toEqual(true)
})

test('should apply to custom object properties', () => {
Copy link
Member

Choose a reason for hiding this comment

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

This test tests nodes again, not regular js objects. Please add tests for primitives as well, like bool, null, string, float etc.

@oskarhane oskarhane force-pushed the run-cypher-on-web-workers branch from 3e48d4c to 90a069f Compare June 29, 2017 09:18
@oskarhane oskarhane force-pushed the run-cypher-on-web-workers branch from 90a069f to b4202cc Compare August 8, 2017 19:58
@oskarhane
Copy link
Member

I just rebased this over master and things are looking good.
I removed the config UI and renamed the config option to useCypherThread (we might use service worker in the future, so no need to specify what type of thread we use) and enabled it by default.

There's a jumpy behaviour going from spinner -> result that needs to be addressed before I want to merge this.
Also I want to do some more testing with more web browsers.

@oskarhane oskarhane force-pushed the run-cypher-on-web-workers branch from 1eda6b6 to 166034e Compare August 10, 2017 20:26
@oskarhane
Copy link
Member

I've now addressed the "double frame insert animation" I was seeing.
I've also tested in major web browsers and had to Promise support in the worker file for it to work in IE 11.
To have the web worker working when the server serving the files is offline I had to make it an "inline worker". I've found no issues with it so far.

I'm approving this to be merged. Anyone else want to have a last check before merging @pe4cey @irfannuri @akollegger?

I'm merging after lunch tomorrow (CEST) unless someone tells me not to 🐎

@oskarhane oskarhane force-pushed the run-cypher-on-web-workers branch from 4275a13 to 04798c4 Compare August 11, 2017 11:57
@oskarhane oskarhane merged commit 84bc32f into neo4j:3.0 Aug 11, 2017
@oskarhane oskarhane deleted the run-cypher-on-web-workers branch August 11, 2017 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants