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]

Reply via email to