Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Defer node mutation during querying #25479

Merged
merged 68 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1193d2e
Move bootstrap into machine
ascorbic Jun 25, 2020
696d5fa
Add parent span and query extraction
ascorbic Jun 26, 2020
77b7f3b
Add rebuildSchemaWithSitePage
ascorbic Jun 26, 2020
6f97db6
Use values from context
ascorbic Jun 26, 2020
36e8ef4
Remove logs
ascorbic Jun 26, 2020
07d0741
Add redirectListener
ascorbic Jun 26, 2020
cd3a909
Changes from review
ascorbic Jun 26, 2020
d2b615b
Log child state transitions
ascorbic Jun 26, 2020
01280a9
Add state machine for query running
ascorbic Jun 29, 2020
d049022
Changes from review
ascorbic Jul 1, 2020
2dad129
Changes from review
ascorbic Jul 1, 2020
1df526a
Merge branch 'feat/bootstrap-state-machine' into feat/query-state-mac…
ascorbic Jul 1, 2020
26420c4
Switch to reporter
ascorbic Jul 1, 2020
637c642
Merge branch 'master' into feat/bootstrap-state-machine
ascorbic Jul 1, 2020
4ff46bd
Merge branch 'master' into feat/bootstrap-state-machine
ascorbic Jul 1, 2020
7bc5056
Use assertStore
ascorbic Jul 1, 2020
cd82b3f
Merge branch 'feat/bootstrap-state-machine' into feat/query-state-mac…
ascorbic Jul 1, 2020
6f0ab66
Merge branch 'master' into feat/query-state-machine
ascorbic Jul 1, 2020
2ce7b77
Remove unused action
ascorbic Jul 1, 2020
133099d
Remove unusued config
ascorbic Jul 1, 2020
e7f426e
Remove unusued config
ascorbic Jul 1, 2020
7da48a6
Add gql runner reset
ascorbic Jul 1, 2020
8f497be
Merge branch 'feat/query-state-machine' of github.com:gatsbyjs/gatsby…
ascorbic Jul 1, 2020
a4ac2dc
Handle node mutation queuing and batching in state machine
ascorbic Jul 2, 2020
6ca668f
Use new pagedata utils
ascorbic Jul 3, 2020
b701d7c
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
d3e7a59
Use develop queue
ascorbic Jul 3, 2020
ae0ece9
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
28e7691
Merge branch 'master' into feat/query-state-machine
ascorbic Jul 3, 2020
896a2af
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
186eb0c
New xstate syntax
ascorbic Jul 3, 2020
c0acfe1
Work-around xstate bug
ascorbic Jul 3, 2020
12d5f20
Track first run
ascorbic Jul 3, 2020
78463b7
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
8987299
Track first run
ascorbic Jul 3, 2020
dc3fb42
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 3, 2020
fa05e0d
Disable --quiet in e2e
ascorbic Jul 3, 2020
4e8609f
Don't defer node mutation if we're outside the state machine
ascorbic Jul 3, 2020
425eafc
Re-quieten e2e
ascorbic Jul 3, 2020
aadc60b
Listen for query file changes
ascorbic Jul 6, 2020
33dc4fa
Lint
ascorbic Jul 6, 2020
50faf93
Handle webhook
ascorbic Jul 6, 2020
f9bdf3c
Merge branch 'master' into feat/query-state-machine
ascorbic Jul 7, 2020
059798b
Changes from review
ascorbic Jul 7, 2020
da0b2ce
Merge branch 'feat/query-state-machine' into feat/defer-node-mutation
ascorbic Jul 7, 2020
e9bf259
Fix typings
ascorbic Jul 7, 2020
2116857
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 7, 2020
e514a76
Changes from review
ascorbic Jul 7, 2020
9edd938
Typefix
ascorbic Jul 7, 2020
a32dc5f
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 9, 2020
b126a99
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 13, 2020
95e0291
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 13, 2020
9496046
feat(gatsby): Move final parts into develop state machine (#25716)
ascorbic Jul 14, 2020
1cab0be
Resolve api promises
ascorbic Jul 15, 2020
6966e76
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 15, 2020
00a975d
Remove unused action
ascorbic Jul 16, 2020
087ed94
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 16, 2020
c807968
Move logging into helper
ascorbic Jul 16, 2020
69f9a7a
Changes from review
ascorbic Jul 16, 2020
523478a
Manually save db
ascorbic Jul 17, 2020
b814215
Add comments
ascorbic Jul 17, 2020
38c05f5
Remove first run from query running
ascorbic Jul 17, 2020
98936a8
Refactor into separate data layer machines
ascorbic Jul 17, 2020
3f758f5
Fix condition
ascorbic Jul 17, 2020
f12ceec
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 20, 2020
e8ddb4d
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 20, 2020
e450d7f
Merge branch 'master' into feat/defer-node-mutation
ascorbic Jul 20, 2020
0d672fc
Merge remote-tracking branch 'upstream/master' into feat/defer-node-m…
Jul 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
191 changes: 129 additions & 62 deletions packages/gatsby/src/commands/develop-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,10 @@ import reporter from "gatsby-cli/lib/reporter"
import chalk from "chalk"
import telemetry from "gatsby-telemetry"
import express from "express"
import { bootstrapSchemaHotReloader } from "../bootstrap/schema-hot-reloader"
import bootstrapPageHotReloader from "../bootstrap/page-hot-reloader"
import { initTracer } from "../utils/tracer"
import db from "../db"
import { detectPortInUseAndPrompt } from "../utils/detect-port-in-use-and-prompt"
import onExit from "signal-exit"
import queryUtil from "../query"
import queryWatcher from "../query/query-watcher"
import * as requiresWriter from "../bootstrap/requires-writer"
import { waitUntilAllJobsComplete } from "../utils/wait-until-jobs-complete"
import {
userPassesFeedbackRequestHeuristic,
showFeedbackRequest,
Expand All @@ -22,32 +16,33 @@ import { markWebpackStatusAsPending } from "../utils/webpack-status"

import { IProgram } from "./types"
import {
startWebpackServer,
writeOutRequires,
IBuildContext,
initialize,
postBootstrap,
rebuildSchemaWithSitePage,
writeOutRedirects,
startWebpackServer,
} from "../services"
import { boundActionCreators } from "../redux/actions"
import { ProgramStatus } from "../redux/types"
import {
MachineConfig,
AnyEventObject,
assign,
Machine,
DoneEventObject,
interpret,
Actor,
Interpreter,
forwardTo,
State,
} from "xstate"
import { DataLayerResult, dataLayerMachine } from "../state-machines/data-layer"
import { dataLayerMachine } from "../state-machines/data-layer"
import { IDataLayerContext } from "../state-machines/data-layer/types"
import { globalTracer } from "opentracing"
import { IQueryRunningContext } from "../state-machines/query-running/types"
import { queryRunningMachine } from "../state-machines/query-running"
import { IWaitingContext } from "../state-machines/waiting/types"
import { runMutationAndMarkDirty } from "../state-machines/shared-transition-configs"
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
import { buildActions } from "../state-machines/actions"
import { waitingMachine } from "../state-machines/waiting"

const tracer = globalTracer()

Expand Down Expand Up @@ -85,6 +80,7 @@ process.on(`message`, msg => {
})

module.exports = async (program: IProgram): Promise<void> => {
reporter.setVerbose(program.verbose)
const bootstrapSpan = tracer.startSpan(`bootstrap`)

// We want to prompt the feedback request when users quit develop
Expand Down Expand Up @@ -135,31 +131,56 @@ module.exports = async (program: IProgram): Promise<void> => {
initial: `initializing`,
states: {
initializing: {
on: {
// Ignore mutation events because we'll be running everything anyway
ADD_NODE_MUTATION: undefined,
QUERY_FILE_CHANGED: undefined,
WEBHOOK_RECEIVED: undefined,
},
invoke: {
src: `initialize`,
onDone: {
target: `initializingDataLayer`,
actions: `assignStoreAndWorkerPool`,
actions: [`assignStoreAndWorkerPool`, `spawnMutationListener`],
Copy link
Contributor

Choose a reason for hiding this comment

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

mutationlistener feels to me that it should be part of the state machine and not a side-effect. At this point it doesn't show me where or how these things change state of the machine.

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 part of the state machine. It's spawning it as an actor, which means it will continue to run when it changes state, and it dispatches events to the top level machine when mutations are received

},
},
},
initializingDataLayer: {
on: {
ADD_NODE_MUTATION: runMutationAndMarkDirty,
// Ignore, because we're about to extract them anyway
QUERY_FILE_CHANGED: undefined,
},
invoke: {
src: `initializeDataLayer`,
data: ({ parentSpan, store }: IBuildContext): IDataLayerContext => {
return { parentSpan, store, firstRun: true }
data: ({
parentSpan,
store,
firstRun,
webhookBody,
}: IBuildContext): IDataLayerContext => {
return {
parentSpan,
store,
firstRun,
deferNodeMutation: true,
webhookBody,
}
},
onDone: {
actions: `assignDataLayer`,
actions: [`assignServiceResult`, `clearWebhookBody`],
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
target: `finishingBootstrap`,
},
},
},
finishingBootstrap: {
on: {
ADD_NODE_MUTATION: runMutationAndMarkDirty,
// Ignore, because we're about to extract them anyway
QUERY_FILE_CHANGED: undefined,
},
invoke: {
src: async ({
gatsbyNodeGraphQLFunction,
}: IBuildContext): Promise<void> => {
src: async (): Promise<void> => {
// These were previously in `bootstrap()` but are now
// in part of the state machine that hasn't been added yet
await rebuildSchemaWithSitePage({ parentSpan: bootstrapSpan })
Expand All @@ -168,30 +189,28 @@ module.exports = async (program: IProgram): Promise<void> => {

startRedirectListener()
bootstrapSpan.finish()
await postBootstrap({ parentSpan: bootstrapSpan })

// These are the parts that weren't in bootstrap

// Start the createPages hot reloader.
bootstrapPageHotReloader(gatsbyNodeGraphQLFunction)

// Start the schema hot reloader.
bootstrapSchemaHotReloader()
},
onDone: {
target: `runningQueries`,
},
},
},
runningQueries: {
on: {
QUERY_FILE_CHANGED: {
actions: forwardTo(`run-queries`),
},
},
invoke: {
id: `run-queries`,
src: `runQueries`,
data: ({
program,
store,
parentSpan,
gatsbyNodeGraphQLFunction,
graphqlRunner,
websocketManager,
firstRun,
}: IBuildContext): IQueryRunningContext => {
return {
Expand All @@ -201,6 +220,7 @@ module.exports = async (program: IProgram): Promise<void> => {
parentSpan,
gatsbyNodeGraphQLFunction,
graphqlRunner,
websocketManager,
}
},
onDone: {
Expand All @@ -210,57 +230,104 @@ module.exports = async (program: IProgram): Promise<void> => {
},
doingEverythingElse: {
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
invoke: {
src: async ({ workerPool, store, app }): Promise<void> => {
src: async (): Promise<void> => {
// All the stuff that's not in the state machine yet

await writeOutRequires({ store })
boundActionCreators.setProgramStatus(
ProgramStatus.BOOTSTRAP_QUERY_RUNNING_FINISHED
)

await db.saveState()

await waitUntilAllJobsComplete()
requiresWriter.startListener()
db.startAutosave()
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
queryUtil.startListeningToDevelopQueue({
graphqlTracing: program.graphqlTracing,
})
queryWatcher.startWatchDeletePage()

await startWebpackServer({ program, app, workerPool, store })
},
onDone: [
{
target: `startingDevServers`,
cond: ({ compiler }: IBuildContext): boolean => !compiler,
ascorbic marked this conversation as resolved.
Show resolved Hide resolved
},
{
target: `waiting`,
},
],
},
},
startingDevServers: {
invoke: {
src: `startWebpackServer`,
onDone: {
target: `waiting`,
actions: `assignServers`,
},
},
},
waiting: {
on: {
ADD_NODE_MUTATION: {
actions: forwardTo(`waiting`),
},
QUERY_FILE_CHANGED: {
actions: forwardTo(`waiting`),
},
EXTRACT_QUERIES_NOW: {
target: `runningQueries`,
},
},
invoke: {
id: `waiting`,
src: `waitForMutations`,
data: ({
store,
nodeMutationBatch = [],
}: IBuildContext): IWaitingContext => {
return { store, nodeMutationBatch, runningBatch: [] }
},
onDone: {
actions: `assignServiceResult`,
target: `rebuildingPages`,
},
},
},
rebuildingPages: {
invoke: {
src: `initializeDataLayer`,
data: ({ parentSpan, store }: IBuildContext): IDataLayerContext => {
return { parentSpan, store, firstRun: false, skipSourcing: true }
},
onDone: {
actions: assign<IBuildContext, any>({ firstRun: false }),
actions: `assignServiceResult`,
target: `runningQueries`,
},
},
},
},
}

const service = interpret(
Machine(developConfig, {
services: {
initializeDataLayer: dataLayerMachine,
initialize,
runQueries: queryRunningMachine,
// Transitions shared by all states, except where overridden
on: {
ADD_NODE_MUTATION: {
actions: `addNodeMutation`,
},
actions: {
assignStoreAndWorkerPool: assign<IBuildContext, DoneEventObject>(
(_context, event) => {
const { store, workerPool } = event.data
return {
store,
workerPool,
}
}
),
assignDataLayer: assign<IBuildContext, DoneEventObject>(
(_, { data }): DataLayerResult => data
),
QUERY_FILE_CHANGED: {
actions: `markQueryFilesDirty`,
},
}).withContext({ program, parentSpan: bootstrapSpan, app, firstRun: true })
)
WEBHOOK_RECEIVED: {
target: `initializingDataLayer`,
actions: `assignWebhookBody`,
},
},
}

const machine = Machine(developConfig, {
services: {
initializeDataLayer: dataLayerMachine,
initialize,
runQueries: queryRunningMachine,
waitForMutations: waitingMachine,
startWebpackServer: startWebpackServer,
},
actions: buildActions,
}).withContext({ program, parentSpan: bootstrapSpan, app, firstRun: true })

const service = interpret(machine)

const isInterpreter = <T>(
actor: Actor<T> | Interpreter<T>
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/src/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface IProgram {
sitePackageJson: PackageJson
ssl?: ICert
graphqlTracing?: boolean
verbose?: boolean
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
setStore?: (store: Store<IGatsbyState, AnyAction>) => void
}

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/src/query/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ const enqueueExtractedPageComponent = componentPath => {

module.exports = {
calcInitialDirtyQueryIds,
calcDirtyQueryIds,
processPageQueries,
processStaticQueries,
groupQueryIds,
Expand Down
25 changes: 12 additions & 13 deletions packages/gatsby/src/query/query-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
* - Whenever a query changes, re-run all pages that rely on this query.
***/

const _ = require(`lodash`)
const chokidar = require(`chokidar`)

const path = require(`path`)
Expand Down Expand Up @@ -196,8 +195,7 @@ exports.extractQueries = ({ parentSpan } = {}) => {
// During development start watching files to recompile & run
// queries on the fly.

// TODO: move this into a spawned service, and emit events rather than
// directly triggering the compilation
// TODO: move this into a spawned service
if (process.env.NODE_ENV !== `production`) {
watch(store.getState().program.directory)
}
Expand All @@ -222,10 +220,6 @@ const watchComponent = componentPath => {
}
}

const debounceCompile = _.debounce(() => {
updateStateAndRunQueries()
}, 100)

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this debounce completely removed or just moved around?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need to debounce anymore, because we're not actually compiling on these events, just dispatching an event to the state machine to say that we'd like to compile when ready.

const watch = async rootDir => {
if (watcher) return

Expand All @@ -238,13 +232,18 @@ const watch = async rootDir => {
})

watcher = chokidar
.watch([
slash(path.join(rootDir, `/src/**/*.{js,jsx,ts,tsx}`)),
...packagePaths,
])
.watch(
[slash(path.join(rootDir, `/src/**/*.{js,jsx,ts,tsx}`)), ...packagePaths],
{ ignoreInitial: true }
)
.on(`change`, path => {
report.pendingActivity({ id: `query-extraction` })
debounceCompile()
emitter.emit(`QUERY_FILE_CHANGED`, path)
})
.on(`add`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
})
.on(`unlink`, path => {
emitter.emit(`QUERY_FILE_CHANGED`, path)
pvdz marked this conversation as resolved.
Show resolved Hide resolved
})
wardpeet marked this conversation as resolved.
Show resolved Hide resolved

filesToWatch.forEach(filePath => watcher.add(filePath))
Expand Down