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

   ## Summary
   
   The `:grails-data-hibernate7-core:test` worker runs out of heap on 9.0.x (CI 
on `c3b2ea8`: `OutOfMemoryError: Java heap space` in three Hibernate 7 shards, 
Coverage, and the Groovy Snapshot Canary). #16534 proposes recycling the worker 
and raising its heap. This PR fixes what fills the heap instead.
   
   The worker has no `maxHeapSize`, so it runs with Gradle's default 
`-Xmx512m`, not the machine's ergonomic heap. Every destroyed 
`HibernateDatastore` stayed reachable, with its `SessionFactory`, for the rest 
of the JVM, so the live set grows with every spec. The leaks are the same on 
8.0.x, 8.1.x and 9.0.x. 9.0.x just crosses the limit first. Measured locally 
with the CI heap (512m), live heap after full GC near the end of the run:
   
   | Branch | Result | Live after full GC |
   |---|---|---|
   | 8.0.x | passes | up to 465m of 512m |
   | 8.1.x | passes | up to 404m of 512m |
   | 9.0.x | `OutOfMemoryError` at test 3099 after 1096 full GCs | 497m of 512m 
|
   
   ## What kept destroyed datastores reachable
   
   A heap dump taken at the 9.0.x `OutOfMemoryError` held 167 
`SessionFactoryImpl` and 287 `StandardServiceRegistryImpl` instances. Their 
paths to GC roots:
   
   1. **Child datastores in `GormRegistry`** (production). The datastores of a 
datastore's additional connection sources (secondary data sources and schema 
tenants) share its mapping context. `destroy()` only destroys children with a 
different mapping context, so it never destroyed them, and 
`GormRegistry.entityDatastores` / `datastoresByQualifier` kept each child, and 
through `ChildHibernateDatastore.parent` the destroyed datastore. `destroy()` 
now removes them from the registry before closing the enhancer.
   2. **Schema tenant session factories** (production). 
`addTenantForSchemaInternal()` creates a connection source, and with it a 
`SessionFactory`, for each schema tenant outside `connectionSources`. 
`closeConnectionSources()` never reached it, so it stayed open and in 
Hibernate's static `SessionFactoryRegistry`. `destroy()` now closes them.
   3. **`HibernateGormDatastoreSpec.getCollector()`** (tests). Each call builds 
a `StandardServiceRegistry` with a JDBC URL, so Hibernate starts a 
`DriverManagerConnectionProvider` pool whose validation thread keeps the 
registry alive. 120 such threads held 167 registries at the OOM. They are now 
destroyed after each feature.
   4. **TCK teardown** (tests). Specs that unbind the session the TCK 
transaction bound (for example `GrailsSessionContextSpec`, 21 features) made 
the rollback in `GrailsDataHibernate7TckManager.destroy()` throw. 
`GrailsDataTckManager.cleanup()` swallowed it, the datastore was never 
destroyed, and the transaction manager stopped before unbinding its JDBC 
connection holder. The manager now binds the session again before rolling back, 
and always destroys the datastore.
   
   Applications that close and recreate a datastore (several Spring contexts in 
one test JVM, development restarts) are affected by 1 and 2 when they use 
additional data sources or schema-per-tenant multi-tenancy.
   
   ## Tests
   
   - New `HibernateDatastoreDestroySpec`:
     - destroying a datastore removes its secondary data source datastore from 
the GORM registry
     - destroying a datastore closes the session factories of its schema 
tenants, both those resolved at startup and one added with `addTenantForSchema`
   - Both new features fail on unpatched 8.1.x for exactly those reasons.
   - Run locally: the new spec, `GrailsSessionContextSpec`, 
`HibernateDatastoreIntegrationSpec` (Docker), `HibernateDatastoreSpec`, 
`HibernateDatastoreSchemaMultiTenancySpec`, `SchemaTenantGormEnhancerSpec`, 
`SchemaMultiTenantSpec`, `MultipleDataSourceConnectionsSpec`, 
`GormApiAllocationSpec`, `GormEnhancerCleanupSpec`, and two specs that use 
`getCollector()`. `:grails-data-hibernate7-core:codeStyle` passes.
   - On 9.0.x, changes 1 and 3 alone already let the full `hibernate7-core` 
suite pass at 512m (32 full GCs, instead of 1096 full GCs and an 
`OutOfMemoryError`). Results of the full suite on this branch will follow in a 
comment.
   
   ## Not included
   
   - `GormApiAllocationSpec`, `HibernateDatastoreSpec` and 
`HibernateDatastoreIntegrationSpec` still leave a few session factories open 
(6, 2 and 1). They are small, and are left for a follow-up.
   - `grails-data-hibernate5` creates schema tenant session factories the same 
way.
   


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