Hi,

This is just a tiny patch, but it allows great things to do in contrib/. The idea is to make the variable_* system pluggable, which means that it can be overridden via:

  $conf['variable_inc'] = 'sites/all/modules/faster_variables/fb_variable.inc';

and from variable_init() to shutdown will my own variable subsystem take over.

This patch is _not_ changing all bootstrap functions and adding another variable_bootstrap_get, which is used until the system is plugged, but rather keeping the default functions as fallback.

Because in PHP functions are not overwritable, I made it so that if there is a _variable_init / _variable_get / _variable_set / _variable_del defined, then it'll use those, else fallback to the current core implementation (though non-cached for simplicity).

This is again not used for replacing variables table alltogether, but rather give modules like faster_variables (my sandbox project), strongarm, memcache, etc. a chance to hook into the variable subsystem without having to use dirty hacks, which make big problems in strongarm for example. It'll also allow more granular caching and things like this to be implemented as contrib rather than core. While core could still have a great implementation it would be great to be able to start in contrib now.

Affected issues that could be solved with this are (not complete list):

#987768: [PP-1] Optimize variable caching
#79008: make variable prefetching optional
#1062452: strongarm_set_conf() needs to be called sooner
#661442: Document variables that are used before hook_init()

This will also make it possible to implement parts of the new CMI approaches in a backwards compatible way in D7, D6 as contrib modules, so that things can be tested long before D8 is out or stable.

This patch is rolled against 8.x, but is really meant for 7.x and 6.x as well. It is only valid for 8.x of course until the CMI takes over, but it can still be useful for testing CMI approaches also in 8.x until it is ready to be merged.

The patch includes a default include/variable.inc, which is just a copy of the core functions in 8.x. I changed the default variable_* function to be as minimal as possible while still providing a (non-cached) fallback if includes/variable.inc is not yet loaded.

Best Wishes,

Fabian

PS: Patches for 8.x,7.x,6.x follow as first comment when issue number is generated.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Fabianx’s picture

And here are the patches.

Note that the D6 patch of includes/variable.inc is a direct port from D7, so does include the cache stampede protection.
This could be changed of course.

Best Wishes,

Fabian

PS:

EDIT: The D6 patch should not include the cache stampede protection as lock_wait is less than ideal in current D6. I'll change it for D6 as soon as I get feedback on the patch / D8 + D7 have been applied.

catch’s picture

Status: Active » Needs review

There are a lot of issues around with the variables system indeed, and while I'm hoping we can get something good into core, this would allow improvements to happen in contrib projects - from working on some of the core patches it is very hard to get something clean that solves all the issues and passes tests, so allowing contrib to take over might be the better option.

Crell’s picture

Status: Needs review » Needs work

If we're going to swap out the variable include file, just swap it out and include the right file early on as part of the base variable initialization. All of the wrapping _variable_set() and such only adds unnecessary complexity and CPU cycles. It is not possible for the variable implementation to change during a single request anyway.

catch’s picture

If we did that, would have to be in http://api.drupal.org/api/drupal/includes--bootstrap.inc/function/drupal..., the patch would be shorter if we did that, and no private functions, so it makes sense.

It would mean you couldn't use variable_get() for the file include itself, but checking $GLOBALS['conf'] at that low a level is probably worth it to make the change less intrusive.

While we're hoping to nuke variable_get() completely in D8, the current work on the configuration proposal with file based storage, covers only half of what variable_get() is used for - it's also used for tracking volatile state changes (and other stuff) between requests - for use cases that the cache system is not able to cope with - see #1175054: Add a storage (API) for persistent non-configuration state. The system that replaces that stuff is going to have to deal with some of the multiple performance issues that variable_set() currently has in D8 and below (hopefully not memory so much, but definitely balancing read vs. write performance), so making it easier to experiment with that stuff in contrib may assist with the overall effort of replacing it.

Anonymous’s picture

Status: Needs work » Needs review
FileSize
6.94 KB

implements #4.

Status: Needs review » Needs work

The last submitted patch, 1193396-5-variable_inc.patch, failed testing.

Anonymous’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Needs review
FileSize
6.97 KB

woops, this is a 7.x patch only.

RobLoach’s picture

I'd love the variable system to be pluggable, but the method of swapping out PHP files should most likely be replaced with #1497366: Introduce Plugin System to Core and #1497230: Use Dependency Injection to handle object definitions .

Anonymous’s picture

yes, for D8. but this is aimed for D7.

variables in D8 will likely not look anything like the current setup because of CMI and key-value store.

RobLoach’s picture

Ah, didn't even see the 7.x-dev in there. Okay! Carry on! :-)

Anonymous’s picture

Status: Needs review » Closed (won't fix)

talked to webchick about this, she's not keen on this getting in to D7, so marking as won't fix.

i've created a module that implements a less broken variable cache here: https://drupal.org/project/variables_that_suck_less

jonhattan’s picture

Status: Closed (won't fix) » Reviewed & tested by the community

It doesn't hurt. It works. Tests passes. It allows to play with alternatives in contrib.

David Hernández’s picture

+1

The variables system it's outdated and it would be awesome to be able to write modules around it.

webchick’s picture

Oh, not necessarily not keen on it. Just wanted to thinker on it some more, and get some feedback from folks like catch, etc. I'll take a look at your project.

Anonymous’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
7.06 KB

rerolled for HEAD.

what else needs to be done to get this in?

tstoeckler’s picture

Is there a reason this is not going into D8? I am aware that variable_get() is going away eventually, but #1175054: Add a storage (API) for persistent non-configuration state is still far off, and last time I checked (literally) CMI isn't going to be able to nuke variable_get() on its own. So ($better && $has_patch > $perfect && !$has_patch)?!

Anonymous’s picture

I'm not opposed to putting this in D8, but I'd rather we wait until later in the cycle to decide that this is the best we'll get.

Anonymous’s picture

bump. wondering what David_Rothstein will think of this issue for D7.

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Tested and reviewed. Works wonderful. Nice trick with checking $conf directly.

NOTE:

You don't need a core patch for your variables_suck_less by exploiting a hack in hook_boot:

https://drupal.org/sandbox/Fabianx/1172558

The trick is to replace $conf with an ArrayObject ...

The above sandbox approach is still a bad hack and #15 patch really does have _no_ impact at all besides making it possible to plugin another variable system.

REVIEWED and TESTED

Thanks beejebus!

sun’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/includes/bootstrap.inc
@@ -740,6 +740,14 @@ function drupal_settings_initialize() {
+  if (isset($conf['variable_inc']) && file_exists(DRUPAL_ROOT . '/' . $conf['variable_inc'])) {

The additional filestat should be removed. We don't do that for any pluggable subsystem.

Overall, however, I'm not sure whether this makes sense. It has the potential of making the upgrade path to D8 more complex.

Fabianx’s picture

@sun: Why would this make the upgrade path potentially more complicated?

Wouldn't you remove all custom modules including things from settings.php before upgrading anyway?

Anonymous’s picture

@21 - because presumably the upgrade path code will want to do something like:

SELECT * FROM {variable}

which won't work for sites that don't use {variable} to store their data.

my take on this is that sites that use alternative storage will have to migrate their variable data back in to {variable} before doing the upgrade. i don't think the core upgrade code should have to cater for contrib backends.

Anonymous’s picture

which i guess means i think this is still RTBC (with the stat taken out), as i don't think core's upgrade path should be effected.

Anonymous’s picture

Status: Needs work » Needs review
chx’s picture

Status: Needs review » Reviewed & tested by the community

I totally agree with

my take on this is that sites that use alternative storage will have to migrate their variable data back in to {variable} before doing the upgrade. i don't think the core upgrade code should have to cater for contrib backends.

this.

catch’s picture

A contrib backend can also just write variables back to the db too, then it'd be easier to switch back for whatever reason.

Haven't reviewed latest patch here. If this goes in, I will want to bump #1175054: Add a storage (API) for persistent non-configuration state to critical for 8.x, since we'll have a de-facto regression between releases until that's fixed.

David_Rothstein’s picture

What are the chances that someone out there has a script like this:

require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
some_low_level_drupal_function();

Where some_low_level_drupal_function() happens to call variable_get().

That would work OK before this patch (variable_get would fall back on its defaults) but result in a fatal error after it.

Not sure how likely that is, but I thought it was worth bringing up. I've certainly seen (in general) scripts that load some Drupal code to access a convenient API function but don't actually bootstrap Drupal in any way.

Anonymous’s picture

@David_Rothstein - that's possible. but do we really test all our D7 patches against that criteria (must be able to run before DRUPAL_BOOTSTRAP_CONFIGURATION)?

i think it's ok to require custom code like that to add a line to load variable.inc:

require_once DRUPAL_ROOT . '/includes/variable.inc';
chx’s picture

@David_Rothstein absolutely nil. If you do not bootstrap to at least DRUPAL_BOOTSTRAP_CONFIGURATION there is no Drupal function you can sensibly call. Aside from that, if you do not go up to DRUPAL_BOOTSTRAP_CONFIGURATION and yet call variable_get directly or indirectly you have bogus code because DRUPAL_BOOTSTRAP_CONFIGURATION includes settings.php which contains variable overrides.

David_Rothstein’s picture

@David_Rothstein - that's possible. but do we really test all our D7 patches against that criteria (must be able to run before DRUPAL_BOOTSTRAP_CONFIGURATION)?

Well, this patch seems like a fairly unique case, since it's physically moving commonly-used functions out of bootstrap.inc. But I do see what you're saying.

I guess we can probably go ahead with it.

But shouldn't we get this committed to Drupal 8 first? I don't see any reason to let the codebases diverge, especially with so little of Drupal 8 using config() currently and the likelihood that something more variable-like will still need to exist (for #1175054: Add a storage (API) for persistent non-configuration state or otherwise).

David_Rothstein’s picture

@David_Rothstein absolutely nil. If you do not bootstrap to at least DRUPAL_BOOTSTRAP_CONFIGURATION there is no Drupal function you can sensibly call.

Well, I do know of one, because I've done it :)

user_password()

It doesn't happen to call variable_get(), but it easily could.

David_Rothstein’s picture

Another example came up in IRC: Turns out filter_xss() winds up calling variable_get() indirectly. And filter_xss(), like user_password(), is also the kind of function you might call, e.g., via a custom script that runs on a Drupal site that doesn't quite exist yet.

I don't think we have to worry too much about this; people doing this kind of thing are living on the edge anyway, and the fix (manually loading variable.inc) is pretty easy. I think it just means we have to announce this change pretty clearly in the release notes so nobody deploying the next release of Drupal 7 gets surprised by it.

tstoeckler’s picture

As I stated in #16 I agree with committing this to D8 first. If this only goes into D7 we have to open a critical task "Remove variable_get()", which we will probably achieve in D8 anyway, but is a pain because of the thresholds.

Anonymous’s picture

Status: Reviewed & tested by the community » Needs review

@David_Rothstein and @catch - want me to reroll for D8?

sun’s picture

Did anyone test whether Drush still works with this patch applied?

catch’s picture

Version: 7.x-dev » 8.x-dev
Status: Needs review » Needs work

Yeah I'd rather have this in D8 and rip it out later than have the code bases diverge, moving back.

chx’s picture

Status: Needs work » Needs review
FileSize
7.04 KB

Status: Needs review » Needs work

The last submitted patch, 1193396_d8.patch, failed testing.

catch’s picture

@sun, I haven't tested drush with this patch, but I did test #973436: Overzealous locking in variable_initialize(). Any implementation that doesn't pre-load all variables into $conf would break drush vget, however I've posted a patch there at #1607072: Make drush vget compatible with alternative variable implementations which makes it work again.

There are some other parts of the drush variable commands (such as telling you whether a variable was originally set during drush vset) which would additionally break if you completely took things out of the variables table. However I think it's OK to require pluggable backends, at least during D7 to write to the variables table in variable_set() even if they don't use it for normal lookups.

Fabianx’s picture

Version: 8.x-dev » 7.x-dev
Category: Feature request » Task
Issue summary: View changes
Status: Needs work » Needs review

Now that CMI is in, we can safely go back to 7.x to get #23 in.

mgifford’s picture

There's a whitespace error in #23, but it still applies.

mgifford’s picture

23: 1193396-23-variable-inc.patch queued for re-testing.

The last submitted patch, 23: 1193396-23-variable-inc.patch, failed testing.

mgifford’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

It's pretty close, but needs a reroll.

Fabianx’s picture

Assigned: Unassigned » Fabianx

Took another look at #23, That actually has a check to work for all the crazy cases that contrib can come up with in not loading the variable subsystem, unless you:

a) use a custom subsystem for variables (rare, very rare) - though I do have a use case now
b) use one of the hacks to call user_password, filter_xss on the same site

a) OR b) work fine, just a) and b) can potentially break - IF and only if custom variable backends don't write through to the variables table on variable_set.

EDIT: oh, ic the function did exist just by including bootstrap.inc, got it ...

New use case:

- A read-only slave DB that is occasionally allowed to write variables to the master DB

attaching new patch in a moment.

Fabianx’s picture

Status: Needs work » Needs review
FileSize
6.99 KB

Reworked the patch from current core.

Fabianx’s picture

Status: Needs review » Needs work

The last submitted patch, 46: core-make_variable_subsystem-1193396-46.patch, failed testing.

Status: Needs work » Needs review
Fabianx’s picture

Drush indeed calls t() before bootstrap, such custom strings will be retrieved several times.

Just adding a variable_get() will only retrieve them once settings.php is loaded and such the variable subsystem.

The last submitted patch, 46: core-make_variable_subsystem-1193396-46.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 50: core-make_variable_subsystem-1193396-50.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 50: core-make_variable_subsystem-1193396-50.patch, failed testing.

mgifford’s picture

amitgoyal’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
7.7 KB

Reroll of#50.

mgifford’s picture

Patch applies nicely in SimplyTest.me. What's the best way to test this to verify it won't interfere with existing sites?

Fabianx’s picture

I think the simplest is to apply the patch to as many sites as possible and try if simple operations work especially when dealing with early bootstrap modules OR drush.

e.g. xmlsitemap was using variable_get in hook_drush_command() (which never worked anyway as it returned empty $conf), and adding drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION) made it work correctly. (I filed a bug against xmlsitemap.)

Such things ...

chx’s picture

Status: Needs review » Reviewed & tested by the community

This should be good to go, the change in t() has absolutely negligible performance implications because the first t() call in a normal Drupal bootstrap will fill in that $custom_strings[$options['langcode']] so basically add 1 if isset($conf['variable_inc') and 1 function_exists call to the whole bootstrap. But drush calls t before settings are loaded so it's necessary.

David_Rothstein’s picture

Status: Reviewed & tested by the community » Needs review

Drush indeed calls t() before bootstrap, such custom strings will be retrieved several times.
...
e.g. xmlsitemap was using variable_get in hook_drush_command()

So we now have at least two real-life examples of the problem discussed above (the first fixed by a workaround in the patch, the second not fixed here). I feel like we really don't have any idea how many scripts there might be out there that use a low-level function like variable_get() (or something like filter_xss() that calls variable_get()) before formally bootstrapping Drupal...

Reading over this issue, is there any reason we can't do anything more like the original patch (which added private functions), only without the separate include file?

In other words, something like this:

function variable_initialize($conf = array()) {
+  static $alternative_implementation_exists = function_exists('_variable_initialize');
+  if ($alternative_implementation_exists) {
+    return _variable_initialize($conf);
+  }
+
  // NOTE: caching the variables improves performance by 20% when serving
  // cached pages.
  if ($cached = cache_get('variables', 'cache_bootstrap')) {
    $variables = $cached->data;
  }
...

(and similar for the other functions)

It would still only require one line of code in settings.php to load in an alternative implementation - it would just need to do the require_once directly rather than setting $conf['variable_inc'].

So basically, same functionality - but all the API functions remain in bootstrap.inc where they've always been and continue to be available regardless of when someone tries to call them.

Fabianx’s picture

Thanks David.

Yes, I agree the original with the fallback implementation is a better idea for BC compatibility.

chx’s picture

I would float the idea to make variable_initialize and variable_set overrideable but not variable_get for performance reasons. You must support GLOBALS['conf'] anyways because occassionally people will directly use it so there isnt really a good use case for overriding variable_get but if you really must you can make GLOBALS['conf'] something ArrayAccess and work from there.

Fabianx’s picture

For one use case variable_set / variable_del pluggable would work for me.

On the other hand, I think most sites that need to use pluggable variable systems might be fine with two function calls to variable_get() instead of one.

Could decide to not ship that function, and it would not be slow ...