[PCBC-141] 1.1 dp releases not working on phps which do not export the symbol php_json_encode Created: 07/Nov/12  Updated: 10/May/13  Resolved: 10/May/13

Status: Resolved
Project: Couchbase PHP client library
Component/s: library
Affects Version/s: 1.1.0-dp5
Fix Version/s: 1.1.0
Security Level: Public

Type: Bug Priority: Blocker
Reporter: Mark Nunberg Assignee: Matt Ingenthron
Resolution: Duplicate Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   
This affects all php binaries which do not have the symbol "php_json_encode". This includes EL5 and EL6 based linux distributions

The library cannot load because the symbol is not found. Disabling using this symbol also means preventing views from functioning (as well as JSON serialization).

I'd like to note that this is not our bug and not our fault. We are probably not the only extension relying on php_json* functions, and package creators (specifically redhat) should expose this symbol.

Additionally, our configure script checks to see if the PHP_JSON_* constants are defined (and if not, compiles out the relevant code from views - this would make views not work, but not prevent the library from loading).

In any event, we should find a way to work around this, as this bug has been seen quite a bit.

One possible solution would be to call the php-level json encoding function. It may incur a bit of overhead from calling into php, but the function itself is more likely to be there.

 Comments   
Comment by Mark Nunberg [ 07/Nov/12 ]
Currently the only workaround is to compile your own php
Comment by Matt Ingenthron [ 07/Nov/12 ]
One other solution that I'd be okay with is to simply uplevel our minimum version of CentOS/RHEL. Unless there's significant pushback, I really don't see any reason to keep people living with ancient code for longer and longer periods of time.

Dissenting opinions accepted, but at some point you have to say "we don't support that because it's broken", right?

Do you think it's likely that some minimum 5.x EL is fixed enough? Or there's some patchlevel that is fixed enough?

If on the other hand, it's just conscious decision to not expose that symbol and it's only supported through PHP land, then calling up there as a workaround seems okay by me.
Comment by Matt Ingenthron [ 07/Nov/12 ]
Assigning this to the subject matter expert for now. Will hopefully pick it back up soon.
Comment by Matt Ingenthron [ 07/Nov/12 ]
One other question, I don't think so, but is this possibly related to need to put the ini for loading the extension after the php_json as an extension?

Another possible workaround is that if the license is permissive enough, we could just suck in the parts we need? Possible API/ABI issues here I guess, but hard to say.
Comment by Matt Ingenthron [ 07/Nov/12 ]
Duh, I see you proactively addressed the "which release" in the original description. Thanks for that.
Comment by Mark Nunberg [ 07/Nov/12 ]
I've explored that possibility. The problem is that on rhel and centos don't export that symbol. EPEL actually contains a json.so (php-pecl-json), but much to our my chagrin, it does not export that symbol either.

I was thinking about sticking the significant bits inside our own code.. it'd probably be easier to just call the php level function though..

This does seem to be a conscious decision by redhat - as this happens in CentOS 6.3 (EL6) as well. So this is obviously not fixed anywhere.
Comment by Mark Nunberg [ 10/Nov/12 ]
http://review.couchbase.org/#/c/22425/
Comment by Mark Nunberg [ 10/Nov/12 ]
The problem is a bit more intricate it seems :)

Apparently some of the json.so modules I've seen *do* export the php_json_* functions. HOWEVER, apparently php loads these modules with a line similar to dlopen(mod, RTLD_LOCAL..) making their symbols unavailable for other modules to use.

A more complex workaround:

static void (*_json_encode)(smart_str*, zval*, int TSRMLS_DC) = NULL;
static void (*_json_decode)(zval*, char*, int, zend_bool, long TSRMLS_DC) = NULL;

static void _init_json_symbols(void)
{
        zend_module_entry *m_ent;
        int ret;
        if (_json_encode) {
                return;
        }

        ret = zend_hash_find(&module_registry, "json", sizeof("json"), (void**)&m_ent);

        if (ret == FAILURE) {
                fprintf(stderr, "Couldn't load extension..\n");
                abort();
        }

        _json_encode = DL_FETCH_SYMBOL(m_ent->handle, "php_json_encode");
        _json_decode = DL_FETCH_SYMBOL(m_ent->handle, "php_json_decode");

        if (_json_encode == NULL || _json_decode == NULL) {
                fprintf(stderr, "Coudln't find JSON handles!: %s\n", dlerror());
                abort();
        }
}

void php_json_encode(smart_str *buf, zval *value, int options TSRMLS_DC)
{
        _init_json_symbols();
        _json_encode(buf, value, options TSRMLS_CC);
}

void php_json_decode(zval *out, char *buf, int len, zend_bool assoc, long depth TSRMLS_DC)
{
        _init_json_symbols();
        _json_decode(out, buf, len, assoc, depth TSRMLS_CC);
}
Comment by Matt Ingenthron [ 11/Nov/12 ]
Very interesting. Why is this done only on RHEL/CentOS? We should check with some PHP internals folks before we go too far here. Maybe there's some subtlety we don't understand.
Comment by Pierre Joye [ 11/Nov/12 ]
hi!

Not sure what RHEL5/6 does but PHP 5.3.2 has these APIs exported since the very 1st day:

https://github.com/php/php-src/blob/php-5.3.2/ext/json/php_json.h

Also json has always been a default extension in PHP, again something wrong at RHEL.

I would suggest to add a configure check to see if it is exposed and refused to compile if it is missing. To me it is a bug in RHEL (not the 1st weird one :) and would not begin to duplicate code around (especially not for json, to ensure 100% compatibility and get all fixes in time).
Comment by Mark Nunberg [ 11/Nov/12 ]
The real problem is in the first line I mentioned; and it might be a change in the arguments of dlopen. If the json extension is truly loaded before the couchbase one, then the couchbase ext *should* find the appropriate symbols to use.

However it seems that for RHEL (I haven't checked on debian.. debian builds the json ext into the php executable itself, rhel provides it as a loadable module) even though the json extension is already loaded, because there is something weird with either the way it uses dlopen (default php header macro for DL_LOAD uses RTLD_GLOBAL, i.e. make the symbols global to the entire app.. but it might be that RHEL uses RTLD_LOCAL...).

But we're not duplicating code here and I personally like the call-to-php solution (even though it's not the most elegant, it's the most predictable/reliable. For example, I don't know how the snippet in my last post would work on a php that doesn't have json as a DSO)
Comment by Mark Nunberg [ 11/Nov/12 ]
So it seems we're (or is it only me) looking at it from multiply wrong ways :)

(1) RHEL (and debian) modify the default php DL_LOAD from using RTLD_LAZY (which would have avoided giving us this error) to RTLD_NOW

(2) On Debian this is not a problem (at least not for JSON) since it's compiled into the php binary

(3) Even on RHEL, placing extension=json.so before extension=couchbase.so seems to do the trick. This is mentioned in the documentation

(4) For tests, run-tests.php will not work. Unless a specifically crafted ini file is written, run-tests.php will not load the json extension at all. Defining an extra -d parameter will load the json extension; however the script counter-intuitively *appends* this option to the commandline, so one thus effectively has:

-d extension=couchbase.so -d extension=json.so

Coupled with RTLD_NOW, this fails.

I'd still like to fix this in code; as this is a fairly confusing matter; if only to be able to print out a more meaningful user message.
Comment by Pierre Joye [ 11/Nov/12 ]
1) Not sure that should cause this error, as long as the loading order is respected.

2) Debian does it right. JSON is a default builtin extension and should always be available, not optionally.

3) Yes, loading order, that should be documented as such in the couchbase install documentation

4) yes, it is expected, also using a php.ini is always a good thing, to avoid tests failure due to random php.ini being caught

There is nothing to fix in couchbase, calling user land function is horribly slow and should really not be done for such thing.
Comment by Mark Nunberg [ 11/Nov/12 ]
The fix here would be to make users understand this a bit better.

The point isn't that something is broken, but rather that something is confusing. This issue is hinted in the couchbase ext documentation, but seems to be passed over by quite a few people who have run into this.

This seems like a very subtle configuration aspect which users shouldn't have to encounter; or at least something in which we'd be able to show the user an error message.

So without going into a debate of terminology about what's "broken" and whose "fault" it is, this is something that lots of users are seeing, and shouldn't. Such fine minutiae really isn't easy to pick up.

Another option would have been to load the symbols dynamically from either the binary or from the loaded library (depending on the configuration) - however 'php_json_decode' is now no longer an exported symbol (it's defined in the header as an inline wrapper around php_json_decode_ex) in some versions.
Comment by Pierre Joye [ 11/Nov/12 ]
ext dependencies are a very common issue in php, this is well documented. I won't change anything in the code (causing more arms) and clearly document that the json ext, for old broken (it is broken, as it must be builtin and always enabled) distribution packages.
Comment by Matt Ingenthron [ 11/Nov/12 ]
Thanks for all the help here Pierre.

According to Mark, this doesn't affect only old, broken distribution packages. It's even in recent RHEL6*.

Just to fix this with the simplest, most supportable solution perhaps we should test the function at startup time and if it's not there log and exit appropriately? If the distro is loading that lazily, and people want to use that distro, then they need to configure their PHP .ini's correctly, right?

Our log message can be explicit even, saying "make sure json whatever is loaded first with your php.ini".

Thoughts?

* which maybe is arguably old, broken, but it's also arguably not since it's the most recent thing shipped on this particular distro fork
Comment by Matt Ingenthron [ 11/Nov/12 ]
Pierre: one other question-- any 'prior art' here? In other words, surely some other extension must rely on json and has an approach?
Comment by Mark Nunberg [ 11/Nov/12 ]
Unfortunately there is no way to configure RHEL's php to load things lazily.

RHEL Specifically mangles this.

I know the patch says 5.0.4, but this is still in php-5.3.3-14.el6_3.src.rpm:

--- php-5.0.4/Zend/zend.h.dlopen
+++ php-5.0.4/Zend/zend.h
@@ -102,11 +102,11 @@
 # endif

 # if defined(RTLD_GROUP) && defined(RTLD_WORLD) && defined(RTLD_PARENT)
-# define DL_LOAD(libname) dlopen(libname, RTLD_LAZY | RTLD_GLOBAL | RTLD_GROUP | RTLD_WORLD | RTLD_PARENT)
+# define DL_LOAD(libname) dlopen(libname, RTLD_NOW | RTLD_GLOBAL | RTLD_GROUP | RTLD_WORLD | RTLD_PARENT)
 # elif defined(RTLD_DEEPBIND)
-# define DL_LOAD(libname) dlopen(libname, RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND)
+# define DL_LOAD(libname) dlopen(libname, RTLD_NOW | RTLD_GLOBAL | RTLD_DEEPBIND)
 # else
-# define DL_LOAD(libname) dlopen(libname, RTLD_LAZY | RTLD_GLOBAL)
+# define DL_LOAD(libname) dlopen(libname, RTLD_NOW | RTLD_GLOBAL)
 # endif
 # define DL_UNLOAD dlclose
 # if defined(DLSYM_NEEDS_UNDERSCORE)
~


So basically by the time our library loads, if it utilizes a bare 'json_decode' that reference is resolved immediately; our module has no chance to warn.
Comment by Matt Ingenthron [ 12/Nov/12 ]
Given there is no good solution, for now we'll need to document this one very, very well.
Comment by kzeller [ 12/Nov/12 ]
I don't do the release notes for individual SDK libraries.
Comment by Matt Ingenthron [ 12/Nov/12 ]
This isn't a release note item, this is a PHP documentation item. This needs to be added to the getting started guide with a really clear description of what the issue is and how to work around it for RHEL/CentOS.

I think you're still able to help us with PHP documentation, right?
Comment by kzeller [ 12/Nov/12 ]
I see. Do you want this to go as a note during the platform-specific install section under RHEL/Centos?
Comment by Matt Ingenthron [ 12/Nov/12 ]
I'll follow the docs team's guidance on how best to present the info.

Just be aware that it's something we've frequently hit and nearly every CentOS/RHEL user will hit it too. It's not our bug really that we can't make it simpler. It's a limitation in current PHP extension loading.

That's why I think it needs to be covered in the appropriate section of the getting started guide, which is both on the web site (http://www.couchbase.com/develop/php/next) and in our documentation (http://www.couchbase.com/docs/couchbase-sdk-php-1.1/download.html and http://www.couchbase.com/docs/couchbase-sdk-php-1.1/installation-verification.html). Most people will hit it when following either of those.

Our web pages currently have the following note:
Note: With the PHP packages on many Red Hat/CentOS distributions (and possibly others), PHP's JSON encoding is not available to other extensions by default. As a result, you may see an error resolving the php_json_encode symbol. The solution is to edit ini file that loads the JSON extension (typically /etc/php.d/json.ini) to add the Couchbase extension after the JSON extension.

Something along these lines (but improved, if you think need be) should be added to the getting started guide in the documentation.
Comment by Matt Ingenthron [ 12/Nov/12 ]
Note, Pierre replied via email to my 'prior art' question:

No, ext dep manager is a long due todo but much easier to document than to implement.

Other core exts have this, exif and mbstring, pdo exts and the sin pdo ext (which should be builtin but rhel and defiant made it wrong 1st)
Comment by kzeller [ 14/Nov/12 ]
Hi,

I'm adding this. Do we have an example of the php_json_encode error that you will get? Need to add this to the section.
Comment by kzeller [ 14/Nov/12 ]
Added to getting started/install as:

"If you are using the PHP SDK on a Linux distribution such as Red Hat/CentOS, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the .ini file that loads the JSON extension to add the Couchbase extension after the JSON extension. For instance, if your JSON extension is at /etc/php.d/json.ini, add the following line to the file under extensions:

extension=/path/to/couchbase.so"
Comment by kzeller [ 14/Nov/12 ]
Added to getting started and install for PHP 1.1:

If you are using the PHP SDK on a Linux distribution such as Red Hat/CentOS, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the .ini file that loads the JSON extension to add the Couchbase extension after the JSON extension. For instance, if your JSON extension is at /etc/php.d/json.ini, add the following line to the file under extensions:

extension=/path/to/couchbase.so
Comment by Matt Ingenthron [ 28/Nov/12 ]
Karen: per the email thread with Mark Nunberg the other day, can we update this to have the two line recommendation? Perhaps that's what you've already done?
Comment by kzeller [ 28/Nov/12 ]
Oh yes, I did update per Mark's email several days ago. It is now:

Depending on the platform you are using, you may also need to reference the JSON library in your PHP configuration file.

If you are using the Couchbase PHP SDK on Red Hat/CentOS or their derivatives, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the php.ini file to load the JSON library and also load the Couchbase library. For instance, if your extensions are at /etc/php.ini, add the following two lines to the file:

extension=/path/to/json.so
extension=/path/to/couchbase.so
The reference to the two extensions must be in this specific order.


You can see it here:

http://www.couchbase.com/docs/couchbase-sdk-php-1.1/download.html
Comment by kzeller [ 28/Nov/12 ]
Oh yes, I did update per Mark's email several days ago. It is now:

Depending on the platform you are using, you may also need to reference the JSON library in your PHP configuration file.

If you are using the Couchbase PHP SDK on Red Hat/CentOS or their derivatives, be aware that JSON encoding for PHP is by default not available to other extensions. As a result you will receive an error resolving the php_json_encode symbol. The solution is to edit the php.ini file to load the JSON library and also load the Couchbase library. For instance, if your extensions are at /etc/php.ini, add the following two lines to the file:

extension=/path/to/json.so
extension=/path/to/couchbase.so
The reference to the two extensions must be in this specific order.


You can see it here:

http://www.couchbase.com/docs/couchbase-sdk-php-1.1/download.html
Comment by Andrey Nikishaev [ 17/Dec/12 ]
Use version 1.1.1 of client lib and still get this error even when couchbase.so loaded after json.so

CentOS 5.6, PHP 5.2.17, Couchbase 1.8
Comment by kzeller [ 17/Dec/12 ]
Hi Matt,

This was reported as an technical issue still with PHP SDK:

   [ http://www.couchbase.com/issues/browse/PCBC-141?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=46067#comment-46067 ]

Andrey Nikishaev commented on PCBC-141:
---------------------------------------

Use version 1.1.1 of client lib and still get this error even when couchbase.so loaded after json.so
Comment by Mark Nunberg [ 17/Dec/12 ]
what's the exact error you're getting?

Maybe the json module needs to be installed as well? (I'll need to check this) --
Comment by Matt Ingenthron [ 17/Dec/12 ]
Andrey: We do not support PHP 5.2, so you'll want to try 5.3 or later. CentOS 5.6 does have, if I recall correctly, a "php53" package.
Comment by Trond Norbye [ 10/May/13 ]
There are multiple bugs reported for this issue
Generated at Sun Aug 31 00:11:48 CDT 2014 using JIRA 5.2.4#845-sha1:c9f4cc41abe72fb236945343a1f485c2c844dac9.