Skip to content

Commit 3ca49f2

Browse files
Moocarwardpeet
authored andcommitted
refactor(gatsby): Make query queue constructable (#13061)
## Description #13004 requires that page queries be executed _after_ the javascript build completes. To get there, we need to be able to process a batch of queries. The current code relies on a global query queue, and uses complex global events to ensure it's paused/resumed during bootstrap. This PR makes the queue constructable so that a batch of queries can be processed easily. I also noticed that `develop` queue is much more complex that the `build` queue, so I've separated those into two queue constructors. ## Related Issues - Sub-PR of #13004 - builds on #13016
1 parent 9a75aff commit 3ca49f2

File tree

7 files changed

+203
-215
lines changed

7 files changed

+203
-215
lines changed

packages/gatsby/src/bootstrap/index.js

+1-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const md5File = require(`md5-file/promise`)
77
const crypto = require(`crypto`)
88
const del = require(`del`)
99
const path = require(`path`)
10-
const convertHrtime = require(`convert-hrtime`)
1110
const Promise = require(`bluebird`)
1211
const telemetry = require(`gatsby-telemetry`)
1312

@@ -33,7 +32,6 @@ process.on(`unhandledRejection`, (reason, p) => {
3332

3433
const { extractQueries } = require(`../query/query-watcher`)
3534
const { runInitialQueries } = require(`../query/page-query-runner`)
36-
const queryQueue = require(`../query/query-queue`)
3735
const { writePages } = require(`../query/pages-writer`)
3836
const { writeRedirects } = require(`./redirects-writer`)
3937

@@ -459,20 +457,7 @@ module.exports = async (args: BootstrapArgs) => {
459457
parentSpan: bootstrapSpan,
460458
})
461459
activity.start()
462-
const startQueries = process.hrtime()
463-
queryQueue.on(`task_finish`, () => {
464-
const stats = queryQueue.getStats()
465-
activity.setStatus(
466-
`${stats.total}/${stats.peak} ${(
467-
stats.total / convertHrtime(process.hrtime(startQueries)).seconds
468-
).toFixed(2)} queries/second`
469-
)
470-
})
471-
// HACKY!!! TODO: REMOVE IN NEXT REFACTOR
472-
emitter.emit(`START_QUERY_QUEUE`)
473-
// END HACKY
474-
runInitialQueries(activity)
475-
await new Promise(resolve => queryQueue.on(`drain`, resolve))
460+
await runInitialQueries(activity)
476461
activity.end()
477462

478463
require(`../redux/actions`).boundActionCreators.setProgramStatus(

packages/gatsby/src/commands/develop.js

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const db = require(`../db`)
3535
const telemetry = require(`gatsby-telemetry`)
3636
const detectPortInUseAndPrompt = require(`../utils/detect-port-in-use-and-prompt`)
3737
const onExit = require(`signal-exit`)
38+
const pageQueryRunner = require(`../query/page-query-runner`)
39+
const queryQueue = require(`../query/queue`)
3840
const queryWatcher = require(`../query/query-watcher`)
3941

4042
// const isInteractive = process.stdout.isTTY
@@ -90,6 +92,7 @@ async function startServer(program) {
9092
await bootstrap(program)
9193

9294
db.startAutosave()
95+
pageQueryRunner.startListening(queryQueue.makeDevelop())
9396
queryWatcher.startWatchDeletePage()
9497

9598
await createIndexHtml()

packages/gatsby/src/query/page-query-runner.js

+80-71
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,20 @@
11
// @flow
22

33
import type { QueryJob } from "../query-runner"
4-
5-
/**
6-
* Jobs of this module
7-
* - Ensure on bootstrap that all invalid page queries are run and report
8-
* when this is done
9-
* - Watch for when a page's query is invalidated and re-run it.
10-
*/
11-
124
const _ = require(`lodash`)
13-
14-
const queue = require(`./query-queue`)
5+
const Queue = require(`better-queue`)
6+
const convertHrtime = require(`convert-hrtime`)
157
const { store, emitter } = require(`../redux`)
8+
const queryQueue = require(`./queue`)
169

1710
let queuedDirtyActions = []
1811

19-
let active = false
20-
let running = false
21-
2212
const runQueriesForPathnamesQueue = new Set()
23-
exports.queueQueryForPathname = pathname => {
13+
const queueQueryForPathname = pathname => {
2414
runQueriesForPathnamesQueue.add(pathname)
2515
}
2616

27-
// Do initial run of graphql queries during bootstrap.
28-
// Afterwards we listen "API_RUNNING_QUEUE_EMPTY" and check
29-
// for dirty nodes before running queries.
30-
exports.runInitialQueries = async () => {
31-
active = true
32-
await runQueries(true)
33-
return
34-
}
35-
36-
const runQueries = async (initial = false) => {
37-
// Don't run queries until bootstrap gets to "run graphql queries"
38-
if (!active) {
39-
return
40-
}
41-
17+
const calcQueries = (initial = false) => {
4218
// Find paths dependent on dirty nodes
4319
queuedDirtyActions = _.uniq(queuedDirtyActions, a => a.payload.id)
4420
const dirtyIds = findDirtyIds(queuedDirtyActions)
@@ -71,13 +47,9 @@ const runQueries = async (initial = false) => {
7147

7248
runQueriesForPathnamesQueue.clear()
7349

74-
// Run these paths
75-
await runQueriesForPathnames(pathnamesToRun)
76-
return
50+
return pathnamesToRun
7751
}
7852

79-
exports.runQueries = runQueries
80-
8153
emitter.on(`CREATE_NODE`, action => {
8254
queuedDirtyActions.push(action)
8355
})
@@ -86,26 +58,6 @@ emitter.on(`DELETE_NODE`, action => {
8658
queuedDirtyActions.push({ payload: action.payload })
8759
})
8860

89-
const runQueuedActions = async () => {
90-
if (active && !running) {
91-
try {
92-
running = true
93-
await runQueries()
94-
} finally {
95-
running = false
96-
if (queuedDirtyActions.length > 0) {
97-
runQueuedActions()
98-
}
99-
}
100-
}
101-
}
102-
exports.runQueuedActions = runQueuedActions
103-
104-
// Wait until all plugins have finished running (e.g. various
105-
// transformer plugins) before running queries so we don't
106-
// query things in a 1/2 finished state.
107-
emitter.on(`API_RUNNING_QUEUE_EMPTY`, runQueuedActions)
108-
10961
let seenIdsWithoutDataDependencies = []
11062

11163
// Remove pages from seenIdsWithoutDataDependencies when they're deleted
@@ -147,10 +99,11 @@ const findIdsWithoutDataDependencies = () => {
14799
return notTrackedIds
148100
}
149101

150-
const runQueriesForPathnames = pathnames => {
102+
const makeQueryJobs = pathnames => {
151103
const staticQueries = pathnames.filter(p => p.slice(0, 4) === `sq--`)
152104
const pageQueries = pathnames.filter(p => p.slice(0, 4) !== `sq--`)
153105
const state = store.getState()
106+
const queryJobs = []
154107

155108
staticQueries.forEach(id => {
156109
const staticQueryComponent = store.getState().staticQueryComponents.get(id)
@@ -162,16 +115,14 @@ const runQueriesForPathnames = pathnames => {
162115
componentPath: staticQueryComponent.componentPath,
163116
context: { path: staticQueryComponent.jsonName },
164117
}
165-
queue.push(queryJob)
118+
queryJobs.push(queryJob)
166119
})
167120

168121
const pages = state.pages
169-
let didNotQueueItems = true
170122
pageQueries.forEach(id => {
171123
const page = pages.get(id)
172124
if (page) {
173-
didNotQueueItems = false
174-
queue.push(
125+
queryJobs.push(
175126
({
176127
id: page.path,
177128
jsonName: page.jsonName,
@@ -186,18 +137,7 @@ const runQueriesForPathnames = pathnames => {
186137
)
187138
}
188139
})
189-
190-
if (didNotQueueItems || !pathnames || pathnames.length === 0) {
191-
return Promise.resolve()
192-
}
193-
194-
return new Promise(resolve => {
195-
const onDrain = () => {
196-
queue.removeListener(`drain`, onDrain)
197-
resolve()
198-
}
199-
queue.on(`drain`, onDrain)
200-
})
140+
return queryJobs
201141
}
202142

203143
const findDirtyIds = actions => {
@@ -221,3 +161,72 @@ const findDirtyIds = actions => {
221161
)
222162
return uniqDirties
223163
}
164+
165+
const runInitialQueries = async activity => {
166+
const pathnamesToRun = calcQueries(true)
167+
if (pathnamesToRun.length === 0) {
168+
return
169+
}
170+
171+
const queryJobs = makeQueryJobs(pathnamesToRun)
172+
173+
const queue = queryQueue.makeBuild()
174+
175+
const startQueries = process.hrtime()
176+
queue.on(`task_finish`, () => {
177+
const stats = queue.getStats()
178+
activity.setStatus(
179+
`${stats.total}/${stats.peak} ${(
180+
stats.total / convertHrtime(process.hrtime(startQueries)).seconds
181+
).toFixed(2)} queries/second`
182+
)
183+
})
184+
await queryQueue.processBatch(queue, queryJobs)
185+
}
186+
187+
/////////////////////////////////////////////////////////////////////
188+
// Listener for gatsby develop
189+
190+
// Initialized via `startListening`
191+
let listenerQueue
192+
193+
/**
194+
* Run any dirty queries. See `calcQueries` for what constitutes a
195+
* dirty query
196+
*/
197+
const runQueuedQueries = () => {
198+
if (listenerQueue) {
199+
listenerQueue.push(makeQueryJobs(calcQueries(false)))
200+
}
201+
}
202+
203+
/**
204+
* Starts a background process that processes any dirty queries
205+
* whenever one of the following occurs:
206+
*
207+
* 1. A node has changed (but only after the api call has finished
208+
* running)
209+
* 2. A component query (e.g by editing a React Component) has
210+
* changed
211+
*
212+
* For what constitutes a dirty query, see `calcQueries`
213+
*/
214+
const startListening = queue => {
215+
// We use a queue to process batches of queries so that they are
216+
// processed consecutively
217+
listenerQueue = new Queue((queryJobs, callback) =>
218+
queryQueue
219+
.processBatch(queue, queryJobs)
220+
.then(() => callback(null))
221+
.catch(callback)
222+
)
223+
224+
emitter.on(`API_RUNNING_QUEUE_EMPTY`, runQueuedQueries)
225+
}
226+
227+
module.exports = {
228+
runInitialQueries,
229+
startListening,
230+
runQueuedQueries,
231+
queueQueryForPathname,
232+
}

packages/gatsby/src/query/query-queue.js

-106
This file was deleted.

0 commit comments

Comments
 (0)