Strange update / compatibility issue
-
Hi,
I discovered a strange but critical issue / incompatibility related to the plugin.
WooCommerce back in stock notifications (official plugin)
We use WooCommerce Back in stock notifications (https://woo.com/de-de/products/back-in-stock-notifications/) to inform subscribers if a product is back in stock. If stock is 0 and stock get’s set to a value >0, this triggers a ‘product is back in stock’ email to everyone who has subscribed to get informed about availability.This works 100% reliably when changing the stock
- via Woo product editor
- via REST API
On the other hand, when updating stock via Zettle, for whatever reason, sending the emails don’t get triggered (at least not lately. I’m pretty sure it worked before, because I’m quite paranoid when it comes to testing integrations before going live).
When checking the product itself, the stock is correct after an update from Zettle.
I now tested this extensively and whatever I do and try, back in stock notifications work perfectly when the stock is updated via prod editor or REST, but not if done via Zettle.
Are you doing something differently when updating the stock compared to the other two methods?
Thanks for your support
Markus
PS: WP 6.4.1, WC 8.3.0, PHP 8.1
-
Hi,
I did some more analysis and there is definitely something wrong with how your plugin handles the stock changes.
I now tested stock updates via:
- Woo product editor
- BEAR bulk editor plugin
- REST API
- direct PHP API calls when calling a page
- direct PHP API calls done asynchronously via cron
All of them work perfectly.
Only when stock updates come from Zettle, everything seems to be screwed for plugins listening to stock updates.This might not be obvious if you test Zettle alone but as soon as there is another plugin which handles stock updates triggered by Zettle, things might become difficult.
I today tested several hours on a blank test environment just with Woo, Zettle, Woo BackInStockNotifier. The issue is 100% reproducible.
As you can imagine, it is quite difficult to understand exactly what is happening when looking into code which I did not write myself.
But I’d like to share what I found out, because this is quite interesting and points to the way how (or better when) your plugin handles stock updates.
The Woo Back-In-Stock (BIS) notifier plugin does sync its internal activities using WordPress shutdown hook.
When using one of the five successful stock update approaches above, I can see that
a) BIS hooks into the stock changes, updates it’s singleton queue and
b) upon shutdown-hook processes the queue for its solely proprietary activities.On the other hand, when updating stock via Zettle, the sequence is:
a) the processing of the queue (triggered via shutdown hook) gets fired first(!!!) and then
b) the update hooks are being processed, filling the queue(!)As your developers might now, the shutdown hook is being executed basically before PHP shuts down execution. After that happens, every subsequent activity is starting a new execution (thus creating new singletons).
In the above case, this means, that instead of the sync being executed as last activity, it gets executed _before_ the stock updates get added to the queue but then, PHP shuts down execution and the queue gets dismissed.
After having looked into your code (which I have to admit, I did not understand completely), I suspect this is happening:
You also hook into shutdown to make stock updates (at least in some cases). E.g. your QueueProcessor is hooked into the shutdown-hook.
This is the only way to explain what I am seeing right now.And if you do that, this is a huge issue, because this way you create race conditions and issues with other plugins.
When you fire stock updates upon PHP shutdown, you hinder other plugins listening to these updates to properly process these.
If you fire them upon shutdown, you must expect that every piece of code listing to the events you initiated is doomed: you’re making critical updates after other plugins might already have been shut down by PHP (or better: their shutdown action already had been completed).
Could you kindly discuss this with your devs?
If I am right (and it really looks like it is happening just as I described), then you might basically break every other plugin which somehow is acting on stock updates.Thanks for your support
Markus
Hi,
I now can finally confirm that the issue stems from your plugin performing stock updates within a shutdown-action thus leading to race conditions in other plugins, because they might already have been shutdown by PHP before you trigger stock changes.
Best
MarkusSUMMARY / CONCLUSION / SOLUTION
Summary- The Zettle plugin uses the shutdown hook to execute stock updates
- During shutdown, the stock updates fire woocommerce_product_set_stock_status, woocommerce_variation_set_stock_status, woocommerce_product_set_stock, woocommerce_variation_set_stock
- Since those events get fired during shutdown, plugins listening to these events likely try to process these _AFTER_ they already have been shut down themselves
Conclusion
The Zettle plugin uses the shutdown hook not only to clean up some isolated stuff but to update data / fire stock update events in an already undefined system state. This must lead to incompatibilities and eventually even inconsistencies with other plugins listening to stock changes.
This is an architectural issue.
Solution
Updating stock during shutdown is a really dangerous practice, because you do that, after other plugins already have been shut down. It might work for you alone but it will 100% create issues with other stock-related plugins using a shutdown action.
Thus, the clean solution would require that you don’t update stock upon shutdown at all, but before.
A quick-fix (which still is not very clean but should work 99,999%) would be the following:
In WebhookListenerEndpoint.php, add PHP_INT_MIN as parameter when registering the shutdown action:
public function registerShutdownHook(Payload $payload): void { /** * We need to signal 200 or Pusher will start to think the webhook is failing. * Hook into shutdown to postpone the actual processing until after the response is sent. */ add_action( 'shutdown', function () use ($payload): void { if (self::$shutDownCalled) { return; } $this->handle($payload); self::$shutDownCalled = true; }, PHP_INT_MIN ); }What this will do is keep your stock updates being executed during shutdown but it will make sure, that your shutdown action is executed prior to every other shutdown action.
By being the first plugin to shut down, you will trigger the stock update hooks _before_ all other plugins will shut down, thus allowing them to process them properly.
I tested this locally and can confirm, that this works.
So, this is just a 12-character-zero-risk-update of your code which will completely eliminate all compatibility / consistency issues created by updating the stock during shutdown.
Could you please include this into a quick update?
This is a really serious behavior and the effect is difficult to spot.
Thus, not heaving heard about this before does not mean, it does not happen.Thanks
MarkusPS: there seem to be multiple places where you register a shutdown action which process the queue, if I’m not mistaken. You should apply the fix to all of them, if these places could result in the same issue
-
This reply was modified 2 years, 6 months ago by
markisu72.
Hello @markisu72,
Thank you for reaching out to us, we are here to help.
The problem you described is a bit technical, so I had to put this in front of our dev team to get an answer on the matter. We do not do much development these days and believe our plugin is performing properly under a vanilla setup. This is what my team responded:
First, that is a spectacular write-up. A lot of effort went into it, and it was an easy and extremely comprehensive read.
The main issue appears to be this part:
When using one of the five successful stock update approaches above, I can see that
a) BIS hooks into the stock changes, updates it’s singleton queue and
b) upon shutdown-hook processes the queue for its solely proprietary activities.Naturally, if we only produce stock updates AFTER the BIS plugin has finished working, it cannot recognize them. However, the part where it says “we have an architectural issue that causes problems with other plugins” is not correct in my opinion. We need to do it this way because we need to produce a 200 response ASAP and postpone processing until after the output has already been sent to the client. I am willing to bet that the BIS plugin wants to accomplish the same thing when it also hooks into
shutdown: Ensuring a fast response for clients while still getting some heavy lifting done. The only difference is that WE have a good excuse for not simply dropping a message into WP-Cron or Action Scheduler.The suggestion to (at the very least) call our logic as early as possible so other plugins have a chance to act on these events appears sound, however. If it helps with third-party compatibility, this is something we could do that should not affect us in any negative way.
If you want to help us set this request up to improve the plugin, please open a private engagement, so we can discuss further how to set up our test environment to reproduce your issue, so we can clearly identify the issue for our dev team to take a look at.
Let me know if this is ok with you.
Kind regards,
JoostHi Joost,
thanks for your answer!
The hint regarding the “architectural issue” was not meant to be offensive, nor did I mean that hooking into shutdown is an issue in general. And yes, BIS uses the same approach.
It is just like (I assume) because your plugin name starts with a ‘z’, your shutdown action is the last to be executed and then firing stock update hooks will hit all other plugins in their shut down state… it somehow inverts the natural execution order.
Sure, I will open a request.
Thanks,
Markus
Thank you Markus,
I’ve received your private support request and we will continue from there.
Kind regards,
Joost
The topic ‘Strange update / compatibility issue’ is closed to new replies.