jdaugherty commented on code in PR #16208:
URL: https://github.com/apache/grails-core/pull/16208#discussion_r4067266991
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -174,7 +203,11 @@ public class MongoDatastore extends AbstractDatastore
implements MappingContext.
protected final GormEnhancer gormEnhancer;
protected final ConnectionSources<MongoClient,
MongoConnectionSourceSettings> connectionSources;
protected final FlushModeType defaultFlushMode;
- protected final Map<String, MongoDatastore> datastoresByConnectionSource =
new LinkedHashMap<>();
+ /**
Review Comment:
Verified — `buildIndex()` checks `closed` before either mode, so the
synchronous branch can no longer run against the client `close()` is closing,
and it gets the same warning the asynchronous one always had.
`BuildIndexesLifecycleSpec` pins it. Resolving.
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -1269,6 +1698,35 @@ public void close() {
}
}
+ /**
+ * Interrupts the background index build, if there is one. A build can run
for minutes, so shutdown
+ * must not wait for it; the server carries on building what it was asked
for.
+ *
+ * @return whether a build was running or queued, and so was cut short
+ */
+ private boolean shutDownIndexBuild() {
+ ExecutorService executor = this.indexBuildExecutor;
+ if (executor == null) {
+ return false;
+ }
+ boolean cutShort = indexBuildsInFlight.get() > 0;
Review Comment:
Verified — `resumeIndexBuild()` returns straight away when the executor was
never shut down, so a connection added while the datastore was stopped costs
`start()` nothing, and the wait is a second rather than ten.
`BuildIndexesPerConnectionSpec` pins the added-while-stopped case. Resolving.
A build that ignores its interrupt still gets a second one alongside it
after that second, which is the right trade at that length.
##########
grails-data-mongodb/docs/src/docs/asciidoc/querying/queryIndexes.adoc:
##########
@@ -142,6 +142,107 @@ WARNING: Dropping and recreating an index rebuilds it
from scratch, during which
A change to a TTL index's `expireAfterSeconds` is handled automatically and
does not require `recreateOnConflict`, because GORM updates the expiry in place
rather than rebuilding the index.
+==== Disabling Index Creation on Startup
+
+
+By default GORM creates and reconciles every index declared in a mapping block
when the datastore starts. Set `buildIndexes` to `false` in
`grails-app/conf/application.yml` to switch that off:
+
+[source,yaml]
+----
+grails:
+ mongodb:
+ buildIndexes: false
+----
+
+or, in `application.groovy`:
+
+[source,groovy]
+----
+grails {
+ mongodb {
+ buildIndexes = false
+ }
+}
+----
+
+With this setting no `createIndex` or `collMod` command is issued for any
domain class, and the indexes already present on the server are left exactly as
they are. Queries are unaffected and continue to use whichever indexes exist.
This is useful when deploying against live data whose indexes are managed
separately — by a DBA or a migration step — so that a deployment does not build
an index against a large production collection, and so an application running
against an older index set does not have those indexes reconciled underneath it.
+
+It also suppresses index creation for domain classes registered after startup.
Declared at the top level the setting applies to every connection, and each
connection can override it:
+
+[source,yaml]
+----
+grails:
+ mongodb:
+ buildIndexes: false
+ connections:
+ reporting:
+ url: mongodb://localhost/reporting
+ buildIndexes: true
+----
+
+NOTE: Because nothing is created, a collection that has never been initialised
with `buildIndexes` enabled will have no declared indexes at all. Turn the
setting off only where the indexes are already in place or are applied by other
means. The setting governs only the indexes GORM derives from the mapping
blocks; an explicit `createIndex` call made by application code against a
collection is unaffected.
+
+==== Building Indexes in the Background
+
+
+MongoDB answers a `createIndex` command only once the index has been built, so
by default the thread that creates the datastore — in an application, the
startup thread — waits for every declared index before the application finishes
starting. On an empty collection that is instant; on a large existing
collection an index build can take minutes, and a deployment waits for all of
them in turn.
+
+Set `buildIndexesAsync` to have the startup index build run on a background
thread instead:
+
+[source,yaml]
+----
+grails:
+ mongodb:
+ buildIndexesAsync: true
+----
+
+Startup then continues without waiting. The indexes are still built one at a
time per connection, on a daemon thread named after its connection, such as
`gorm-mongo-index-build-default-1`. Different connections can build indexes
concurrently, including when they use the same MongoDB server. The thread is
released after the build finishes and it has been idle for one second.
+
+Two consequences are worth planning for:
+
+* A query issued before its index has been built is served without it —
correctly, but with the performance of an unindexed query. The same applies to
a `unique` index: it constrains nothing until the build finishes.
+* Because startup no longer waits, a failure to build an index can no longer
fail startup. It is logged at error level, and the application runs without
that index. With the default synchronous build the exception propagates and the
application does not start.
+
+The setting is ignored when `buildIndexes` is `false`, and it applies only to
the index build performed when the datastore starts — a domain class registered
after startup is indexed on the thread registering it.
+
+NOTE: If the application shuts down while a background build is still running,
GORM stops waiting for it, but the server carries on building the index it was
asked for. A datastore stopped for a checkpoint and restored (with CRaC) runs a
build it cut short again once it is restarted, on every connection.
+
+==== What the Index Build Reports
+
+
+An index build that finishes without error logs one summary line at `INFO`:
+
+----
+Index build for database [myDb] finished in 412ms: 2 created, 5 already
present, from 3 domain class(es)
+----
+
+The domain class count includes every class considered for indexing, including
classes that declare no indexes. The created and already-present counts
describe index declarations; declaring the same keys twice can report one
created and one already present.
+
+The elapsed time is what the caller actually spent waiting — startup with the
default settings, or the background thread when `buildIndexesAsync` is enabled,
where this line is also the only signal that the build has finished.
+
+The split between created and already present is what makes that time
interpretable. MongoDB answers a `createIndex` for an index it already has
immediately and without building anything, so a restart that changed no
mappings reports everything as already present and costs milliseconds; a line
reporting indexes created is the one that accounts for a slow start. An index
that `recreateOnConflict` dropped and built again costs as much as a new one,
and is counted apart as recreated:
+
+----
+Index build for database [myDb] finished in 9315ms: 0 created, 1 recreated, 6
already present, from 3 domain class(es)
+----
+
+If any declaration failed, the summary is logged at `WARN` instead and reports
how many; the failures themselves are logged individually as they happen. A
build that stops partway — a lost connection or a timeout — reports how far it
got before stopping, also at `WARN`:
+
+----
+Index build for database [myDb] did not finish, stopping after 812ms at 1 of 3
domain class(es): 1 created, 0 already present
+----
+
Review Comment:
This paragraph and the summary format above it are now one count short.
A collection whose listing fails no longer takes the whole build's breakdown
with it — its declarations are counted apart, and the line gains a third token:
```
Index build for database [myDb] finished in 412ms: 2 created, 5 already
present, 3 applied without a listing, from 3 domain class(es)
```
Nothing in the guide says what that is, and it is the one token an operator
cannot guess from its wording: "applied without a listing" reads like work that
was skipped, when it means the opposite — the declarations were applied, and
only the created/already-present split for that one collection is missing,
because `listIndexes` on it failed.
The sentence here is also narrower than the behaviour now is. The `N index
declaration(s) applied` fallback is what you get when *nothing* was classified
— the logger at `WARN`, or no collection readable at all — rather than
specifically a build with failures. The new count is the partial case between
the two, so both are worth a line.
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -574,19 +631,331 @@ public ConnectionSources<MongoClient,
MongoConnectionSourceSettings> getConnecti
}
/**
- * Builds the MongoDB index for this datastore
+ * Builds the MongoDB index for this datastore.
+ *
+ * <p>Each index is created by a command that the server answers only once
the index has been built,
+ * so with the default settings this blocks whoever creates the datastore
— in an application, the
+ * startup thread — for as long as MongoDB takes to build every declared
index. Enabling
+ * {@code grails.mongodb.buildIndexesAsync} hands the work to a background
thread and returns
+ * immediately instead.
*/
public void buildIndex() {
+ if (!buildIndexes) {
+ LOG.info("Index creation is disabled by [{} = false]. The indexes
declared by the domain classes " +
+ "will not be created or reconciled; the indexes already
present on the server are left untouched.",
+ MongoSettings.SETTING_BUILD_INDEXES);
+ return;
+ }
+ ExecutorService executor = this.indexBuildExecutor;
+ if (executor == null) {
+ runIndexBuild(null);
+ return;
+ }
+ if (!executor.isShutdown()) {
+ LOG.info("Building the indexes declared by the domain classes on a
background thread ([{} = true]). " +
Review Comment:
Verified — all four lines name the connection, the disabled message names
the per-connection key with the top level one beside it, and the announcement
is logged once `execute` has returned. `BuildIndexesPerConnectionSpec` pins the
name on a named connection's announcement. Resolving.
One consequence of the move is worth knowing: for a build that finishes in a
few milliseconds, the worker's own lines can now reach the log ahead of the
announcement that it started. Logging it as the first thing the submitted task
does would keep it both true and in order.
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -574,19 +631,331 @@ public ConnectionSources<MongoClient,
MongoConnectionSourceSettings> getConnecti
}
/**
- * Builds the MongoDB index for this datastore
+ * Builds the MongoDB index for this datastore.
+ *
+ * <p>Each index is created by a command that the server answers only once
the index has been built,
+ * so with the default settings this blocks whoever creates the datastore
— in an application, the
+ * startup thread — for as long as MongoDB takes to build every declared
index. Enabling
+ * {@code grails.mongodb.buildIndexesAsync} hands the work to a background
thread and returns
+ * immediately instead.
*/
public void buildIndex() {
+ if (!buildIndexes) {
+ LOG.info("Index creation is disabled by [{} = false]. The indexes
declared by the domain classes " +
+ "will not be created or reconciled; the indexes already
present on the server are left untouched.",
+ MongoSettings.SETTING_BUILD_INDEXES);
+ return;
+ }
+ ExecutorService executor = this.indexBuildExecutor;
+ if (executor == null) {
+ runIndexBuild(null);
+ return;
+ }
+ if (!executor.isShutdown()) {
+ LOG.info("Building the indexes declared by the domain classes on a
background thread ([{} = true]). " +
+ "Startup does not wait for them, so a query issued before
its index exists is served without it.",
+ MongoSettings.SETTING_BUILD_INDEXES_ASYNC);
+ try {
+ executor.execute(() -> runIndexBuild(executor));
+ return;
+ }
+ catch (RejectedExecutionException e) {
+ // Shut down between the check and the submission.
+ }
+ }
+ if (closed) {
+ LOG.warn("An index build was requested after the datastore was
closed, so it was not started.");
+ }
+ else {
+ indexBuildPending = true;
+ LOG.info("An index build was requested while the datastore is
stopped; it will run when the datastore is restarted.");
+ }
+ }
+
+ /**
+ * Runs one build and reports it, whether it finished or not.
+ *
+ * @param executor the executor running it, or {@code null} for a build on
the caller's thread, whose
+ * failure is left to propagate to the caller
+ */
+ private void runIndexBuild(ExecutorService executor) {
+ long startedAt = System.nanoTime();
+ // Telling a created index from one that was already there costs a
listIndexes per indexed
+ // collection, and its only product is the summary, so it is skipped
when that would not be logged.
+ IndexBuildSummary summary = new IndexBuildSummary(LOG.isInfoEnabled());
+ Exception failure = null;
+ try {
+ buildDeclaredIndexes(summary);
+ }
+ catch (Exception e) {
+ // An Error is left to propagate as it is, without the summary.
+ failure = e;
+ if (e instanceof InterruptedException) {
+ // Caught here rather than by whoever interrupted, so the flag
it cleared is put back.
+ Thread.currentThread().interrupt();
+ }
+ }
+ long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() -
startedAt);
+ if (failure == null) {
+ logFinishedIndexBuild(summary, elapsedMillis);
+ return;
+ }
+ if (executor == null) {
+ logUnfinishedIndexBuild(summary, elapsedMillis, false);
+ // Unchanged, checked or not: an initializeIndices override may
throw one without declaring it.
+ MongoDatastore.<RuntimeException>rethrow(failure);
+ return;
+ }
+ // Nothing is waiting on this thread, so an error that would have
failed startup has to be
+ // reported here or it is lost entirely.
+ boolean abandoned = executor.isShutdown() ||
Thread.currentThread().isInterrupted();
Review Comment:
Verified — `abandoned` now needs both the executor to be down and a failure
the shutdown accounts for, the cause chain is walked, and
`BuildIndexesBackgroundFailureSpec` pins a duplicate key arriving during a
close being reported as the failure it is rather than swallowed. Resolving.
Two things I would not hold anything up for. `IllegalStateException`
anywhere in the chain counts as explained by the shutdown, so an
`initializeIndices` override that throws one of its own while the datastore is
closing is still downgraded — narrower than the old behaviour, and the driver's
own is the case that matters, so this is only worth knowing. And the loop's
cycle guard breaks a cause that is its own cause but not a longer cycle;
`Throwable.initCause` rules the first out and nothing in the driver builds the
second.
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -574,19 +631,331 @@ public ConnectionSources<MongoClient,
MongoConnectionSourceSettings> getConnecti
}
/**
- * Builds the MongoDB index for this datastore
+ * Builds the MongoDB index for this datastore.
+ *
+ * <p>Each index is created by a command that the server answers only once
the index has been built,
+ * so with the default settings this blocks whoever creates the datastore
— in an application, the
+ * startup thread — for as long as MongoDB takes to build every declared
index. Enabling
+ * {@code grails.mongodb.buildIndexesAsync} hands the work to a background
thread and returns
+ * immediately instead.
*/
public void buildIndex() {
+ if (!buildIndexes) {
+ LOG.info("Index creation is disabled by [{} = false]. The indexes
declared by the domain classes " +
+ "will not be created or reconciled; the indexes already
present on the server are left untouched.",
+ MongoSettings.SETTING_BUILD_INDEXES);
+ return;
+ }
+ ExecutorService executor = this.indexBuildExecutor;
+ if (executor == null) {
+ runIndexBuild(null);
+ return;
+ }
+ if (!executor.isShutdown()) {
+ LOG.info("Building the indexes declared by the domain classes on a
background thread ([{} = true]). " +
+ "Startup does not wait for them, so a query issued before
its index exists is served without it.",
+ MongoSettings.SETTING_BUILD_INDEXES_ASYNC);
+ try {
+ executor.execute(() -> runIndexBuild(executor));
+ return;
+ }
+ catch (RejectedExecutionException e) {
+ // Shut down between the check and the submission.
+ }
+ }
+ if (closed) {
+ LOG.warn("An index build was requested after the datastore was
closed, so it was not started.");
+ }
+ else {
+ indexBuildPending = true;
+ LOG.info("An index build was requested while the datastore is
stopped; it will run when the datastore is restarted.");
+ }
+ }
+
+ /**
+ * Runs one build and reports it, whether it finished or not.
+ *
+ * @param executor the executor running it, or {@code null} for a build on
the caller's thread, whose
+ * failure is left to propagate to the caller
+ */
+ private void runIndexBuild(ExecutorService executor) {
+ long startedAt = System.nanoTime();
+ // Telling a created index from one that was already there costs a
listIndexes per indexed
+ // collection, and its only product is the summary, so it is skipped
when that would not be logged.
+ IndexBuildSummary summary = new IndexBuildSummary(LOG.isInfoEnabled());
+ Exception failure = null;
+ try {
+ buildDeclaredIndexes(summary);
+ }
+ catch (Exception e) {
+ // An Error is left to propagate as it is, without the summary.
+ failure = e;
+ if (e instanceof InterruptedException) {
+ // Caught here rather than by whoever interrupted, so the flag
it cleared is put back.
+ Thread.currentThread().interrupt();
+ }
+ }
+ long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() -
startedAt);
+ if (failure == null) {
+ logFinishedIndexBuild(summary, elapsedMillis);
+ return;
+ }
+ if (executor == null) {
+ logUnfinishedIndexBuild(summary, elapsedMillis, false);
+ // Unchanged, checked or not: an initializeIndices override may
throw one without declaring it.
+ MongoDatastore.<RuntimeException>rethrow(failure);
+ return;
+ }
+ // Nothing is waiting on this thread, so an error that would have
failed startup has to be
+ // reported here or it is lost entirely.
+ boolean abandoned = executor.isShutdown() ||
Thread.currentThread().isInterrupted();
+ if (abandoned) {
+ indexBuildPending = true;
+ // toString rather than the message: an interrupted driver call
can arrive wrapped in
+ // an exception that carries no message of its own.
+ LOG.debug("The background index build was abandoned because the
datastore is shutting down: {}",
+ failure.toString(), failure);
+ }
+ else {
+ LOG.error("The background index build failed: {}. The application
is running without the " +
+ "indexes that were not created.", failure.getMessage(),
failure);
+ }
+ logUnfinishedIndexBuild(summary, elapsedMillis, abandoned);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Exception> void rethrow(Exception failure)
throws E {
+ throw (E) failure;
+ }
+
+ private void logFinishedIndexBuild(IndexBuildSummary summary, long
elapsedMillis) {
+ if (summary.applied() == 0 && summary.recreated == 0 &&
summary.failures == 0) {
+ LOG.debug("No indexes are declared by the {} domain class(es)
mapped to database [{}]",
+ summary.entities, defaultDatabase);
+ return;
+ }
+ if (summary.failures == 0) {
+ LOG.info("Index build for database [{}] finished in {}ms: {}, from
{} domain class(es)",
+ defaultDatabase, elapsedMillis, summary.describe(),
summary.entities);
+ }
+ else {
+ LOG.warn("Index build for database [{}] finished in {}ms: {}, from
{} domain class(es). " +
+ "The failures are reported above.",
+ defaultDatabase, elapsedMillis, summary.describe(),
summary.entities);
+ }
+ }
+
+ /**
+ * Reports how far a build got before it stopped, which is what an
operator needs when a background
+ * build fails partway: the error names the cause, this says what was
applied before it.
+ */
+ private void logUnfinishedIndexBuild(IndexBuildSummary summary, long
elapsedMillis, boolean abandoned) {
+ String message = "Index build for database [{}] did not finish,
stopping after {}ms at {} of {} domain class(es): {}";
+ if (abandoned) {
+ LOG.debug(message, defaultDatabase, elapsedMillis,
summary.entities, summary.entitiesTotal, summary.describe());
+ }
+ else {
+ LOG.warn(message, defaultDatabase, elapsedMillis,
summary.entities, summary.entitiesTotal, summary.describe());
+ }
+ }
+
+ /**
+ * Creates and reconciles the indexes declared by every entity mapped to
this datastore. MongoDB answers
+ * each {@code createIndex} only once the index exists, so the time this
takes is the time the caller —
+ * startup, or the background build thread — actually spends waiting.
+ */
+ private void buildDeclaredIndexes(IndexBuildSummary summary) {
+ List<PersistentEntity> entities = new ArrayList<>();
for (PersistentEntity entity :
this.mappingContext.getPersistentEntities()) {
// Only create Mongo templates for entities that are mapped with
Mongo
- if (!entity.isExternal()) {
- if (entity.isMultiTenant() && multiTenancyMode ==
MultiTenancySettings.MultiTenancyMode.SCHEMA) continue;
-
+ if (!entity.isExternal() &&
+ !(entity.isMultiTenant() && multiTenancyMode ==
MultiTenancySettings.MultiTenancyMode.SCHEMA)) {
+ entities.add(entity);
+ }
+ }
+ summary.entitiesTotal = entities.size();
+ IndexBuildSummary previousSummary = indexBuildSummary.get();
+ indexBuildSummary.set(summary);
+ try {
+ for (PersistentEntity entity : entities) {
initializeIndices(entity);
+ summary.entities++;
+ }
+ }
+ finally {
+ if (previousSummary == null) {
+ indexBuildSummary.remove();
+ }
+ else {
+ indexBuildSummary.set(previousSummary);
}
}
}
+ /**
+ * The indexes a collection already had when the build reached it, listed
once on first use and then
+ * reused. {@code createIndex} is idempotent and answers the same way
whether or not it had to build
+ * anything — the driver hands back only the index name, discarding the
{@code numIndexesBefore} /
+ * {@code numIndexesAfter} the server reports — so what was there
beforehand is what distinguishes an
+ * index this build created from one it merely confirmed.
+ *
+ * <p>Listed lazily so that an entity declaring no indexes costs no round
trip, and not at all when the
+ * summary is not being classified. Successful changes are recorded so
later declarations on the same
+ * keys see the current name and TTL, and are not counted as new indexes.
A conflict re-lists instead
+ * of trusting the snapshot: whatever the server reports as conflicting
may have appeared since.
+ */
+ private static final class ExistingIndexes {
+
+ private final com.mongodb.client.MongoCollection<Document> collection;
+
+ private final IndexBuildSummary summary;
+
+ private List<Document> indexes;
+
+ private boolean listed;
+
+ /** Why the last listing failed, for the conflict it leaves
unreconciled to report. */
+ private RuntimeException listingFailure;
+
+ private ExistingIndexes(com.mongodb.client.MongoCollection<Document>
collection, IndexBuildSummary summary) {
+ this.collection = collection;
+ this.summary = summary;
+ }
+
+ /**
+ * @return the known current indexes, or {@code null} if they could
not be listed
+ */
+ private List<Document> get() {
+ return listed ? indexes : refresh();
+ }
+
+ /**
+ * Lists the indexes from the server now, replacing what was known.
+ *
+ * @return the current indexes, or {@code null} if they could not be
listed
+ */
+ private List<Document> refresh() {
+ listed = true;
+ try {
+ indexes = collection.listIndexes().into(new ArrayList<>());
+ listingFailure = null;
+ } catch (RuntimeException e) {
+ // Not fatal: the build can still create indexes, it just
cannot report which of them
+ // were new. Losing the breakdown is not worth failing a
startup over.
+ LOG.debug("Could not list the existing indexes of collection
[{}]: {}",
+ collection.getNamespace().getCollectionName(),
e.getMessage(), e);
+ indexes = null;
+ listingFailure = e;
+ summary.classified = false;
Review Comment:
Verified — `readable` is per `ExistingIndexes` and is cleared by a failed
listing whichever caller made it, `contains()` answers `null` for a collection
it cannot classify, and the summary keeps the rest of the breakdown alongside a
count of what it could not classify. `BuildIndexesPartialClassificationSpec`
gets `1 created, 0 already present, 1 applied without a listing, 1 failed` out
of one collection listed, one refused and one that loses the re-listing its
conflict makes. Resolving.
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -1282,7 +1740,14 @@ public void close() {
* @return The {@link ConnectionSources}
*/
protected static ConnectionSources<MongoClient,
MongoConnectionSourceSettings> createDefaultConnectionSources(MongoClient
mongoClient, PropertyResolver configuration, MongoMappingContext
mappingContext, boolean closeable) {
- MongoConnectionSourceSettings settings = new
MongoConnectionSourceSettings();
+ // Bound from the configuration rather than left at the defaults: the
client is supplied here, but
+ // the settings that describe how the datastore behaves (multiTenancy,
stateless, transactional,
+ // buildIndexes, engine, flush mode) still come from grails.mongodb
with grails.gorm fallbacks,
+ // exactly as they do when GORM creates the client itself. The
connection details in them are
+ // unused - this client is already connected - so a configured URL is
dropped: its database would
+ // otherwise take precedence over the mapping context's, which is the
one this path always used.
+ MongoConnectionSourceSettings settings =
buildConnectionSourceSettings(configuration);
+ settings.url(null);
Review Comment:
Verified — the note says the connection details are the exception, which
four settings a connection without its own `url` falls back to, and that each
should declare one. Resolving.
##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -166,6 +174,38 @@ public class MongoDatastore extends AbstractDatastore
implements MappingContext.
protected final boolean stateless;
protected final boolean codecEngine;
protected final boolean transactionsEnabled;
+ protected final boolean buildIndexes;
+ protected final boolean buildIndexesAsync;
+
+ /**
+ * Runs the startup index build off the thread that creates the datastore
when
+ * {@code grails.mongodb.buildIndexesAsync} is enabled; {@code null}
otherwise. A single thread,
+ * so the indexes are still built one at a time per connection. The worker
expires after one idle
+ * second, releasing the worker while allowing subsequent calls to {@link
#buildIndex()}.
+ *
+ * <p>Not final: a shut down executor cannot be restarted, so {@link
#start()} replaces the one
+ * {@link #stop()} shut down.
+ */
+ private volatile ExecutorService indexBuildExecutor;
Review Comment:
Verified — `volatile`, with the reason beside it. Resolving.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]