-
-
Notifications
You must be signed in to change notification settings - Fork 824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(dev/core#174) Full PSR-16 compliance for ArrayCache, SqlGroup, Redis, Memcache, APC #12360
Conversation
(Standard links)
|
CRM/Utils/Cache/SqlGroup.php
Outdated
// CRM_Core_BAO_Cache::setItem($value, $this->group, $key, $this->componentID); | ||
$lock = Civi::lockManager()->acquire("cache.{$this->group}_{$key}._null"); | ||
if (!$lock->isAcquired()) { | ||
CRM_Core_Error::fatal(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton @totten should we throw exception here rather than fatal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seamuslee001 Probably. Thinking out loud:
- The system is generally simpler/clearer with exceptions.
- If there's anyone using
SqlGroup
who's trying to catch/handle problems, they could be surprised by a switch fromfatal
toexception
. - OTOH, very few things try to handle fatals. (Because they're awkward to handle.) And when they are handled, that's by converting the fatal to an exception. (Not strictly so - but in practical terms, that's all I've ever seen or heard of.)
- Overall, the previous contract for
CRM_Utils_Cache_Interface
was unclear about how errors would be reported. PSR-16 pushes us to standardize (i.e. returning booleans and throwing particular exception-interfaces). - I suspect real-world error-handling behaviors in
CRM/Utils/Cache/*
have been pretty varied (depending on the particular driver/configuration, you might get exceptions or fatals or FALSE return values). Any problem that rises from "making the error handling more consistent" is likely to be in the category of "the problem existed before but manifested differently". - We don't often get to highlight the cache-subsystem as an area of change. It may be net-gentler on downstream to rip-off this particular bandaid rather than kick it down the road.
So... yes, I'll update to use the exception instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seamuslee001 updated and squashed revisions to report exceptions instead of fatals. This also adds a concrete class to implement \Psr\SimpleCache\CacheException
.
d903350
to
319f6f9
Compare
jenkins, test this please |
319f6f9
to
8d28c09
Compare
Rebased with some updates:
|
8d28c09
to
269d50c
Compare
ad7a6dc
to
975a7fe
Compare
375ff42
to
ec7313e
Compare
@totten are you able to rebase this now please? |
ec7313e
to
5b05b32
Compare
CRM/Utils/Cache/Memcached.php
Outdated
throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed: " . $this->_cache->getResultMessage()); | ||
} | ||
else { | ||
Civi::log()->error("Redis set ($key) failed: " . $this->_cache->getResultMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@totten i think this should be Memcached here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Fixed and squashed.
8602436
to
5914e0e
Compare
Overview ---------------------------------------- When using forms based on CiviQuickForm (specifically `CRM_Core_Controller`), `CRM_Core_Session` stores form-state via `CRM_Core_BAO_Cache::storeSessionToCache` and `::restoreSessionFromCache`, which in turn calls `CRM_Core_BAO_Cache::setItem()` and `::getItem()`. However, using `CRM_Core_BAO_Cache::setItem()` and `::getItem()` means that all session state **must** be written to MySQL. For dev/core#174, we seek the **option** to store via Redis/Memcache. Before ---------------------------------------- * (a) Form/session state is always stored via `CRM_Core_BAO_Cache::setItem()` and `civicrm_cache` table. * (b) To ensure that old sessions are periodically purged, there is special purpose logic that accesses `civicrm_cache` (roughly `delete where group_name=Sessions and created_date < now()-ttl`). * (c) On Memcache/Redis-enabled systems, the cache server functions as an extra tier. The DB provides canonical storage for form/session state. After ---------------------------------------- * (a) Form/session state is stored via `CRM_Utils_CacheInterface`. * On a typical server, this defaults to `CRM_Utils_Cache_SqlGroup` and `civicrm_cache` table. * (b) To ensure that old sessions are periodically purged, the call to `CRM_Utils_CacheInterface::set()` specifies a TTL. * It is the responsibility of the cache driver to handle TTLs. With civicrm#12360, TTL's are supported in `ArrayCache`, `SqlGroup`, and `Redis`. * (c) On Memcache/Redis-enabled systems, the cache server provides canonical storage for form/session state.
I wish we had stats on the use of these - my guess is it would be better to move some out to an extension at some point |
CRM/Utils/Cache.php
Outdated
* @throws \CRM_Utils_Cache_InvalidArgumentException | ||
*/ | ||
public static function assertValidKey($key) { | ||
$strict = CRM_Utils_Constant::value('CIVICRM_PSR16_STRICT', FALSE) || defined('CIVICRM_TEST'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this
if ($default !== NULL) { | ||
throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default"); | ||
CRM_Utils_Cache::assertValidKey($key); | ||
$result = apc_fetch($this->_prefix . $key, $success); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$success does not seem to be defined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like some metaphysical management widsom. ;)
More seriously, $success
is actually an output of this line. apc_fetch($key, &$success) sets the value of $success
to indicate whether it worked.
* @throws \CRM_Utils_Cache_InvalidArgumentException | ||
*/ | ||
private function assertIterable($func, $keys) { | ||
if (!is_array($keys) || $keys instanceof Traversable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this should be reversed ie
While you cannot implement this interface, you can use it in your checks to determine if something is usable in for each. Here is what I use if I'm expecting something that must be iterable via foreach.
in the example not !items
http://php.net/manual/en/class.traversable.php
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, good catch! This explains why testGetMultipleWithGenerator()
,testSetMultipleWithGenerator()
, and testDeleteMultipleGenerator()
weren't working. :)
I've pushed up a patch to fix assertIterable
and re-enable those tests.
… BAO multitier cache.
There are two drivers, `CRM_Utils_Memcache` and `CRM_Utils_Memcached`. It's nice to update them in tandem (with similar design decisions). If an admin admin is experimenting/debugging, this consistency makes it easier to switch between drivers. (Cache data written by one driver can be read by the other driver.) In addition to the standard PSR-16-style changes, there are a couple changes in how data is formatted when written to memcache: * To allow support for targetted `flush()`ing (one prefix at a time), we update the naming convention per https://github.com/memcached/memcached/wiki/ProgrammingTricks#deleting-by-namespace This means that a typical key includes a bucket-revision code: * BEFORE: `<site-prefix>/<bucket-prefix>/<item-key>` (`dmaster/default/mykey`) * BEFORE: `<site-prefix>/<bucket-prefix>/<bucket-revision>/<item-key>` (`dmaster/default/5b33011fea555/mykey`) * Values are `serialize()`d. This resolves an ambiguity where `Memcache::get()` does not let us know if it returns `FALSE` because there's an error because that's the stored value. By serializing, those scenarios can be distinguished. * `get(...) === FALSE` means "item was not found" * `get(...) === serialize(FALSE)` means "item was found with value FALSE"
One noteable quirk is that APC retains expired (per TTL) records until the following page-request. To comply with the test suite, we have to double-check the expiration time.
aadd265
to
b6d78ec
Compare
Most of this has been split-out to smaller PRs. There was one last bit, which I've now put in #12389, so we'll close this one. |
Overview ---------------------------------------- When using forms based on CiviQuickForm (specifically `CRM_Core_Controller`), `CRM_Core_Session` stores form-state via `CRM_Core_BAO_Cache::storeSessionToCache` and `::restoreSessionFromCache`, which in turn calls `CRM_Core_BAO_Cache::setItem()` and `::getItem()`. However, using `CRM_Core_BAO_Cache::setItem()` and `::getItem()` means that all session state **must** be written to MySQL. For dev/core#174, we seek the **option** to store via Redis/Memcache. Before ---------------------------------------- * (a) Form/session state is always stored via `CRM_Core_BAO_Cache::setItem()` and `civicrm_cache` table. * (b) To ensure that old sessions are periodically purged, there is special purpose logic that accesses `civicrm_cache` (roughly `delete where group_name=Sessions and created_date < now()-ttl`). * (c) On Memcache/Redis-enabled systems, the cache server functions as an extra tier. The DB provides canonical storage for form/session state. After ---------------------------------------- * (a) Form/session state is stored via `CRM_Utils_CacheInterface`. * On a typical server, this defaults to `CRM_Utils_Cache_SqlGroup` and `civicrm_cache` table. * (b) To ensure that old sessions are periodically purged, the call to `CRM_Utils_CacheInterface::set()` specifies a TTL. * It is the responsibility of the cache driver to handle TTLs. With civicrm#12360, TTL's are supported in `ArrayCache`, `SqlGroup`, and `Redis`. * (c) On Memcache/Redis-enabled systems, the cache server provides canonical storage for form/session state.
Overview
#12342 provides nominal compliance with PSR-16; however, some drivers raise exceptions or warnings for new options . This PR ensures more complete support for PSR-16 in three drivers (
ArrayCache
,SqlGroup
, andRedis
).(
Depends: The patches here work/are cogent, but I've flagged it "Blocked" because it depends/includes other open PRs: #12342, #12348, #12354)Before
ArrayCache
,SqlGroup
,Redis
,Memcache
,Memcached
, andAPCcache
are not tested for compliance -- and would not pass if they were because:$cache->set(...$ttl)
would throw an error any custom TTL.$cache->get(...$default)
woudl throw an error for any not-NULL default.$cache->set()
/$cache->get()
handle objects in ways that are unsafe.$cache->flush()
is overzealous (Memcache); in others, underzealous (APCcache).After
ArrayCache
,SqlGroup
,Redis
,Memcache
,Memcached
, andAPCcache
have been fixed to comply (per SimpleCacheTest), and they comply.Technical Details
SqlGroup
is substantially rewritten. It no longer wraps aroundCRM_Core_BAO_Cache
- instead, it does SQL queries which are similar in nature and uses its own front-cache.CRM_Core_BAO_Cache
do not support TTL/expiration. Adding support effectively requires patching every function to respect TTL in reading+writing both DB and thread-local front-cache. And in the long-run, it's better to maintain that kind of logic in framework that is standard compliant.CRM_Core_BAO_Cache
andCRM_Utils_Cache_SqlGroup
wrap the same DB table but have different front-caches, and if you tried accessing the same records through both interfaces, then there could be cache-coherence bugs. However, if we consider the audit of caches, it becomes less of a concern. Why? Because the use-cases are XOR. (Ex: Thedashboard
cache always uses BAO to read/write cache-rows. Ex: Thejs_strings
cache always uses OOP to read/write cache-rows.)BAO_Cache
could just be a thin wrapper forSqlGroup
which we provide for backward compatibility). However, that will make even bigger PR that's harder to review en masse.SqlGroup
andArrayCache
should work intuitively. ForRedis
,Memcache
, andMemcached
, the test needs a particular environment to run -- which isn't universally available. TheConfiguredMemoryTest
will run against whatever cache service is enabled incivicrm.settings.php
-- and if no service is configured, it will be skipped. (APCcache
is similar, although the specific environmental requirements differ.) For the tests which don't run in the CI environment, I've confirmed they pass locally.