codeconsole opened a new pull request, #16495:
URL: https://github.com/apache/grails-core/pull/16495

   ## Problem
   
   With `-Dspring.context.checkpoint=onRefresh`, Spring takes the CRaC 
checkpoint as the context refreshes: after every bean has been created and 
before it starts any lifecycle bean. Before a checkpoint 
`DefaultLifecycleProcessor` stops only the beans that are running, and at that 
point none are, so nothing opened while beans were being created is closed. An 
application using GORM for MongoDB never got past it. The checkpoint failed, 
and the application did not start:
   
   ```
   org.springframework.context.ApplicationContextException: Failed to take CRaC 
checkpoint on refresh
   Caused by: org.crac.CheckpointException
        Suppressed: 
jdk.internal.crac.mirror.impl.CheckpointOpenSocketException: 
Socket[addr=host.docker.internal/0.250.250.254,port=27017,localport=49302]
        Suppressed: 
jdk.internal.crac.mirror.impl.CheckpointOpenSocketException: 
Socket[addr=host.docker.internal/0.250.250.254,port=27017,localport=49286]
   ```
   
   That is an ordinary MongoDB server, not the embedded one. Two things 
connected while the beans were being created:
   
   - `MongoConnectionSourceFactory` created the `MongoClient` as the datastore 
was built, and the driver starts the monitors that connect to the server as 
soon as a client exists.
   - `MongoDatastore` built the indexes the domain classes declare in its 
constructor.
   
   An embedded MongoDB added a third: `EmbeddedMongoInitializer` binds the 
server before the context refreshes, and `EmbeddedMongoLifecycle` is not 
stopped before this checkpoint either. With `-Dspring.context.exit=onRefresh`, 
Spring halts the JVM at the same point, which runs no shutdown hook, so a 
flapdoodle `mongod` was left running and holding its port.
   
   A checkpoint of a running application already worked, but the client the 
application had been handed did not survive it. GORM closed every client it 
owned before the checkpoint and built replacements after the restore, and the 
`mongo` bean, which the guide shows injected into controllers and services, 
kept the closed one: every call on it after a restore failed with 
`IllegalStateException: state should be: open`.
   
   ## Change
   
   - **`RestartableMongoClient`.** The `MongoClient` GORM creates for a 
connection is now a handle on a driver client that it builds when it is first 
used or started. `stop()` closes the driver client and refuses use until 
`start()` builds a new one, and `close()` is final. The handle stays the same 
object, so the `mongo` bean, a client obtained from 
`MongoDatastore.getMongoClient()`, multi-tenancy and the Spring Data 
integration go on working after a restore. `MongoConnectionSourceFactory`, the 
`Supplier<MongoClient>` constructor the Boot auto-configuration uses and the 
`MongoClientSettings.Builder` constructors all produce one. The connection 
settings are still built and checked as the datastore is created, so a URL that 
cannot be parsed is reported there. A client a custom connection source factory 
builds itself is still closed and replaced as before, and the factory's Javadoc 
says to wrap it instead.
   - **`MongoDatastore` connects in `start()`.** The first start connects the 
client of every connection GORM owns and builds the declared indexes, in the 
datastore's lifecycle phase (`LIFECYCLE_PHASE`, -1000). Spring starts it before 
it publishes `ContextRefreshedEvent`, so the indexes are in place before 
`BootStrap` runs and before the web server accepts a request. A datastore that 
nothing starts, such as one created outside an application context, starts 
itself when the first session is opened on it, so code that creates one and 
queries it finds its indexes as before. A datastore that has been stopped is 
not started again by being used: its clients refuse until it is started, so a 
request arriving during a checkpoint cannot reopen a socket. `isRunning()` is 
`false` until the datastore has started.
   - **`EmbeddedMongoInitializer`.** With `spring.context.checkpoint=onRefresh` 
or `spring.context.exit=onRefresh`, it publishes the URL and leaves starting 
the server to `EmbeddedMongoLifecycle`, which the context starts before the 
datastore (phase -2000). A URL asking for port `0` is given a free port when it 
is published, since the server binds later. Without either property nothing 
changes: the server is listening as soon as the initializer has run, so code 
that talks to MongoDB while beans are still being created finds it.
   
   ## Limits
   
   A checkpoint taken as the context refreshes needs nothing else to have 
connected either. These still do, and the CRaC section of the guide names them:
   
   - A `MongoClient` the application hands to GORM, including the `mongo` bean 
Spring Boot's own `MongoAutoConfiguration` defines, which the Boot 
auto-configuration passes to GORM rather than building a client itself. It is 
connected as it is created, and GORM neither starts nor stops a client it does 
not own.
   - Application code that queries MongoDB while beans are being created, 
rather than in `BootStrap` or later.
   - `MongoConnectionSources`, which reads the connections from MongoDB as the 
datastore is created.
   
   A `MongoDatabase`, `MongoCollection` or `ClientSession` obtained from the 
client belongs to the driver client it came from, so one held across a 
checkpoint is closed with it. The client itself is what to keep.
   
   ## Tests
   
   - `RestartableMongoClientSpec`: nothing is built until the client is used 
(naming, printing and comparing it are not uses); the first use builds one 
driver client and later uses share it; `start()` builds it at once; `stop()` 
closes it, use is refused with a message naming the connection, and `start()` 
serves the same handle from a new one; a client stopped before it was ever used 
builds nothing; `close()` is final; a driver client that fails to build leaves 
the handle untouched; sixteen threads using it for the first time at once share 
one driver client; and every method of `MongoClient` is passed through, checked 
by reflection against a recording driver client, so a method added to the 
driver's interface cannot be left out silently.
   - `ConnectsWhenStartedSpec`: GORM registered through 
`MongoDbDataStoreSpringInitializer`, as a Grails application registers it, 
against a real server, with a driver listener on every client and a lifecycle 
bean at `Integer.MIN_VALUE`, which is where the checkpoint is taken. When that 
bean starts, no client has been created and no command sent; after the refresh 
the datastore is running, has issued `createIndexes`, and the domain class's 
index exists. It fails without the `MongoDatastore` change.
   - `MongoDbDataStoreSpringInitializerSpec`: the `mongo` bean reaches MongoDB 
after the context is stopped and started again, as Spring does around a 
checkpoint, and is still the client GORM uses. It fails with `state should be: 
open` without the `RestartableMongoClient` change.
   - `MongoDbGormAutoConfigurationCloseSpec`: the client the Boot 
auto-configuration builds from Spring Boot's settings does not exist when the 
first lifecycle bean starts, is created when the datastore starts, and stays 
the same client across a stop and start.
   - `MongoDatastoreLifecycleSpec`: a datastore is not running and none of its 
clients, on any connection, is connected until it is started; the first session 
starts a datastore nothing has started; a stopped datastore is not started by a 
session opened on it; clients are restarted in place on every connection, 
including one added at runtime and a named connection beside an 
application-supplied default client; and a client a custom factory builds 
itself is still closed and replaced.
   - `EmbeddedMongoStartedWithTheContextSpec`: with either property set, the 
URL is published and nothing is listening until the context starts; a lifecycle 
bean at `Integer.MIN_VALUE` finds nothing listening; port `0` is published as a 
real port; a port held by something else is reported when the server starts; 
and a server reused by a reloaded context is not started before that context 
starts it. Every feature fails without the initializer change. The properties 
are set as an application sets them, after `DefaultLifecycleProcessor` has been 
loaded, so the contexts the spec refreshes are not checkpointed or halted.
   - The index-build specs that created a datastore and expected its indexes 
from the constructor now start it first, as the application context does. 
`BuildIndexesAsyncSpec` checks that the synchronous build runs on the thread 
that starts the datastore.
   - All tests pass in `grails-data-mongodb-core`, 
`grails-data-mongodb-embedded`, `grails-data-mongodb-spring-boot`, 
`grails-data-mongodb-spring-data` and `grails-data-mongodb`, and in the 
`mongodb/base`, `mongodb/database-per-tenant` and `mongodb/springboot` test 
examples. Checkstyle and CodeNarc are clean for the changed modules. I have not 
run the whole `./gradlew build`.
   
   Verified end to end with a Grails 8.0.0-RC2 application carrying these 
commits, on Azul Zulu CRaC 25, against MongoDB in Docker. Before, 
`-Dspring.context.checkpoint=onRefresh` failed with the exception above. With 
the change the checkpoint is taken before any client exists, the restore 
succeeds, the datastore connects as Spring restarts the lifecycle beans, 
`BootStrap` reads and updates a user, and logging in as that user works.
   
   ## Docs
   
   - The guide's CRaC section covers a checkpoint taken as the context 
refreshes as well as one taken while the application runs, says that the client 
GORM hands out survives a restore (it used to say to obtain it again), and 
lists what still connects early.
   - Index Creation on Startup and Building Indexes in the Background say when 
the datastore starts, and the Embedded MongoDB section says when its server is 
started under the two properties.
   - What's New in the Grails guide has an entry for CRaC support in GORM for 
MongoDB.
   


-- 
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