I am running in a conceptual problem in my development which I previously also encountered in the Drupal CMS and was wondering what would be the best solution to circumvent the problem.
Drupal, and my own Snap Websites software, make use of a publish / subscribe pattern where one can write functions that automatically get called whenever a certain event happens. This can be shown in the following process (note that neither are using threads in those specific processes, it runs within one single main thread in one single main process--although Snap! supports backends—separate processes, I have no concerns with those at this point):
+----+ +----+ +----+ +----+ +----+
| P1 |--->| P2 |--->| P3 |--->| P4 |-...->| Pn |
+----+ +----+ +----+ +----+ +----+
| |
| | p4 signal +------+ +------+ +------+
| +----------->| O1/4 |--->| O2/4 |-...->| On/4 |
| +------+ +------+ +------+
|
| p2 signal +------+ +------+ +------+
+----------->| O1/2 |--->| O2/2 |-...->| On/2 |
+------+ +------+ +------+
Shown here we see that process P2 emits a signal (p2 signal) and that generates functions of observers O12, followed by O22, etc. up to On2 to be called. The exact same thing happens in process P4.
In most cases that works just fine. However, once in a while, a change made by one of the observers is required by the next observer. In other words, the order O12 then O22 is important. However, if the end user has the possibility to switch the two observers around, then the process becomes O22 then O12 which breaks the proper sequence and thus something will not happen.
The Drupal awkward solution is to use a weight to sort their modules in a given order. This is an issue because that forces all observers of that module to happen in that order when some of the events may need to happen before and others after. Drupal modules with equal weights are sorted alphabetically, which at least provides some form of consistency.
It seems to me that the proper way would be to generate signal p2 again if one of the observers made a change that warrant the signal p2 to be re-run. There are two problems in such an implementation:
We have to be careful to not generate an infinite loop, so an observer should not re-generate the signal if what it needs to do was already done in a previous call [this can be complicated though]; and
We are probably going to re-execute the code of many observers for nothing.
Called again:
+----+ +----+ +----+ +----+
| P1 |--->| P2 |--->| P3 |-...->| Pn |
+----+ +----+ +----+ +----+
|
|
|<--------------------------+
| | p2 signal after P2-like change
| |
| p2 signal +------+ +------+ +------+
+----------->| O1/2 |--->| O2/2 |-...->| On/2 |
+------+ +------+ +------+
The other way would be for each observer to tell us whether they should run before or after certain other observers. This means observers have knowledge that they probably should not have, although I am not so sure that this is really a bad thing.
A concrete example of the problem in my Snap! Websites environment:
[Client Side]
- User uploads a PDF file
- Client process (i.e. browser) sends PDF to server
- Server transforms PDF to a preview image
- Client checks to see whether the PDF preview is ready
In step 3 the server process is something like this:
[Server Side--i.e. front end server]
- The Editor plugin receives POST event with the PDF file in it
- The Editor plugin calls a Content function to save the PDF in the database
- A Page plugin observer is called as the Content plugin sends a "created content" signal
- The Page plugin observer links the PDF file to a script that will be used to generate the PDF preview image (this is a Magick++ script, you can define different sizes, quality, flipping, etc.)
- The Images plugin also has an observer on the "created content" signal, that observer detects the link created by the Page plugin observer and mark that PDF for future processing by our Image plugin backend
- The server process returns to the client (i.e. this was very fast, a few ms)
In the Images plugin backend process (a completely separate process which can even run on a computer by itself):
[Backend Side--i.e. server possibly not accessible from the front end]
- The Images plugin backend process wakes up
- The Images plugin backend checks for new data to process and finds the PDF
- The Images plugin backend runs the script attached to the PDF document
- The Images plugin backend saves the preview of the PDF along its other file in the database, ready to be retrieved by the client (step 4 of the first process.)
As we can see in the Server process, step (5) fails if step (4) does not happen before it, which in the Snap Websites environment, at this point, means the Page plugin must be loaded before the Images plugin.