`globalCache` should always use the central connection, but when using a
`database`-driver cache store with `DatabaseTenancyBootstrapper`, it
does not (with the exception of `DatabaseCacheBootstrapper`, explained
below).
`globalCache` creates a fresh `CacheManager` each time it's resolved
(it's a `bind`, not a `singleton`). A freshly-created manager builds its
database stores using the current default DB connection. When
`DatabaseTenancyBootstrapper` is active, that default is `tenant`. So
`globalCache` in tenant context points at the tenant DB. Specifically,
`CachedTenantResolver` stores cached tenant lookups via `globalCache`.
When a domain is deleted in tenant context, the invalidation logic calls
`globalCache->forget(...)`, but that hits the tenant DB, while the
resolver cache entry is in the central DB. `globalCache->forget(...)`
doesn't actually do anything in that case.
With `DatabaseCacheBootstrapper`, this is already handled. `globalCache`
is always central because it sets
`TenancyServiceProvider::$adjustCacheManagerUsing` to a callback that
explicitly restores the central connection on `globalCache`'s stores.
To fix this, after constructing the fresh CacheManager in the
globalCache binding, explicitly set the connection of every
database-driver store to its configured connection value, **falling back
to central_connection** when the config value is null (null value =
inherits whatever the current default DB connection is).
This is sufficient for `CacheTenancyBootstrapper` and any other
bootstrapper that doesn't explicitly set the store's DB connection. For
`DatabaseCacheBootstrapper` specifically, this alone is not enough since
it explicitly sets the store's config connection to 'tenant'. That's why
DatabaseCacheBootstrapper's `$adjustCacheManagerUsing` callback runs
after and overrides those stores back to the original (central)
connection.
> In short: `makeDatabaseCacheStoresCentral()` handles stores with a
`null` connection config (falls back to central).
`$adjustCacheManagerUsing` handles the `DatabaseCacheBootstrapper` case
where the config is explicitly set to 'tenant'.
Added datasets that use the database cache store +
CacheTenancyBootstrapper to the relevant tests (globalCache and
invalidation) to test regression
(0cf7043b73),
and the changes mentioned above
(https://github.com/archtechx/tenancy/pull/1462/commits/5e65c67ea0daf98f57f2a6a7b0e1937bbc397a56)
make these tests pass.
---------
Co-authored-by: Samuel Stancl <samuel@archte.ch>
By purging stores, we "detach" existing cache stores from the
CacheManager, making them impossible to adjust in the future.
We also unnecessarily recreate them on every tenancy bootstrap/revert.
A simpler case where this causes problems is defining a RateLimiter in
a service provider. That injects a single cache store into the
rate limiter singleton, which then becomes a completely independent
object after tenancy is initialized due to the purge. This in turn
means the central and tenant contexts share the rate limiter cache
instead of using separate caches as one would expect.
* Initial implementation (lukinovec)
* Make sure DatabaseCacheBootstrapper runs after DatabaseTenancyBootstrapper, misc wip changes
* Fix withTenantDatabases()
* Add failing test (GlobalCacheTest)
* Configure globalCache's DB stores to use central connection instead of default connection every time it's reinstantiated
* Make GlobalCache facade not cached. Even though it wasn't causing issues
in our existing tests, it likely was flaky, and making it not $cached
makes it now consistent with global_cache() - always getting a new
CacheManager from the globalCache container binding
* Add database connection assertions in GlobalCacheTest
* Run all cached resolver/global cache tests with DatabaseCacheBootstrapper
* Reset adjustCacheManagerUsing in revert() and TestCase
* Reset static $stores property
* Finalize GlobalCache-related changes
* tests: remove pointless cache TTLs
* Refactor DatabaseCacheBootstrapper
* Refactor tests
Co-authored-by: lukinovec <lukinovec@gmail.com>