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]