Hi, First of all, thanks for your time and work on this. Comments bellow.
On Wed, Oct 21, 2015 at 06:26:19PM +0300, George Sedov wrote: > Hi, > > So there was some confusion regarding the bug #753141, and what was the > ideas behind the proposed changes. Let me explain the situation. > > Right now, the lua plugins are working this way: > > 1. When the plugin is asked to perform the operation, lua-factory > constructs the structure named OperationSpec (OS), in where it puts all > the relevant information about the operation, most of it taken directly > from the GrlSourceSearchSpec, BrowseSpec and such. > > 2. The constructed OS is then put into the lua context, into the global > table "_G" with the unique key, constructed from the operation_id (see > https://git.gnome.org/browse/grilo-plugins/tree/src/lua-factory/grl-lua > -library.c#n1584) > > 3. After putting the OS, lua-factory also puts the same data into the > same global table, only this time with the key, which is the same for > all the operations, and with that, that operation is considered > "current" (see https://git.gnome.org/browse/grilo-plugins/tree/src/lua- > factory/grl-lua-library.c#n1658). This function also does the check for > the "unfinalized operations" which I will explain later. > > 3. As part of it's lua API, lua-factory provides several functions that > use the OS data. Most of them are synchronous and just take the OS > which is "current", but there are two of them that require callbacks, > namely grl.fetch and grl.unzip. They save the operation_id of the > "current" operation internally, and use it later in the callback to > retrieve the relevant OS from the global table. They retrieve it by > making their operation "current" again, and after they are done, they > invalidate the "current" operation completely. These function also > increase the special counter of unfinished async calls, to keep the > track of when every particular operation is really "done". > > 4. Now about this "unfinalized operation" check. The idea behind it is, > that by the time the next operation starts, all the synchronous calls > from the previous operation have already been finished. So every OS > that has the counter of unfinished async calls = 0 should be > "finalized", i.e. the finalizing grilo callback must be called. If it's > not, it is called forcibly with the warning of "broken plugin" being > made. > > Apart from the obvious over-complication, this approach has several > flaws. > > 1. The foremost: this approach will totally not work in case of several > operations being run simultaneously. There can be only one "current" > operation, and so if the previous operation didn't finish it's > synchronous part by the time the next operation started, it is > finalized forcibly anyway. Another screw-up will happen if several lua > async callback would be run simultaneously. They will rewrite the > "current" operation and reset it when the first of them would be done. > All in all, the design is absolutely async-unfriendly. All in all, I agree with you. It is over-complicated but I don't see how it could break with several operations from grilo (like, several async search/browse) as each OperationSpec stays in _G related to its operation-id. If you *can* break it easily, tell me how or even better, create a test under lua-factory [1] and attach to the BZ :-) [1] https://git.gnome.org/browse/grilo-plugins/tree/tests/lua-factory Also, what I think is "async-unfriendly" is the "string" as callback without any argument as user_data. We workaround this with a global table in the plugin which make it ugly IMHO. Bad design. > 2. The async methods like grl.fetch can't work without OS, even though > they don't require them directly. So you can't fetch anything during > e.g. grl_source_init() Agreed. Bad design. > 3. The methods that do require OS on the other hand, like > grl.get_options, can be called from everywhere, even though it does > nothing and doesn't make any sense outside of the scope of the > respective operation. > So, the idea is provide all functions under 'grl' table. This is not strange at all and keep everything very simple to the developer. If a function does not make sense to be called then it should not be called - It does not mean that we must control when developer use it. grl.get_options was indeed created thinking that would be called inside an operation handler - thus it would fail otherwise as it would not be able to get OperationSpec. /* broken plugin */ > And now, after we refreshed the current situation, the changes proposed > in the patches from #753141 > > 1. Remove the OS from the global tables, as well as the concept of the > "current" operation. I'm okay with this. > 2. Similarly, remove all the functions that require OS directly from > the global scope. There are actually only four of them: grl.get_options > grl.get_requested_keys grl.get_media_keys and grl.callback. Right. > 3. Now comes the tricky part. To be able to access the OS from inside > these functions, the C closures are constructed for every such > functions with OS as an upvalue, and these closures are then passed to > the lua functions as parameters. From the lua code this will look like > this: > > was: > > function grl_source_search (query) > local count = grl.get_options("count") > grl.callback() > end > > become: > > function grl_source_search (query, options, callback) > local count = options("count") > callback() > end > So, this changes a lot. What I like least is that you are splitting functions provided by grl-lua-library. I do like grl as a library - globally accessible. I might need some studying to closures to realize why this would be a bad idea but why not having the whole grl table as closure then? Also: would it be worse if we set `grl.get_options = options` before calling the operation handler? even if get_options and friends are not accessible afterwards due gc cleanup, that might be better as we keep functions centralized. ** aside of `callback` which is the only one I think it makes sense to keep as an argument here. > 4. Since all the methods that require the access to OS should be passed > to the lua functions as parameters, it is best to make the number of > these methods as few as possible. That's why the methods > grl.get_options grl.get_requested_keys and grl.get_media_keys were > merged into one. otherwise the function would look something like > > function grl_source_search (query, options, requested_keys, media_keys, > callback) > > 5. As a final step, the way the lua async functions are working was > changed. > > was: > > function async_callback (data) > end > > function grl_source_search (query) > grl.fetch("something","async_callback") > end > > become: > > function async_callback (data) > end > > function grl_source_search (query) > > grl.fetch("something",async_callback) > end > > And if it doesn't strike you as a big difference, then consider these > possibilities: > Yes, this is much better indeed. > grl.fetch("something",function(data) print data end) > > or > > local userdata = {} > grl.fetch("something",function(data) async_callback(data, userdata) end) > > There is also provided an option of passing the userdata directly > > function async_callback (data, userdata) > end > > function grl_source_search (query) > grl.fetch("something",async_callback, {}, userdata) > end > > The extra {} is required as an option for grl.fetch > It is optional, not really required. > So, these are the changes that are proposed in bug #753141. As you can > see, they are quite API breaking, no way around it, but I think the > benefits are worth it. > > Cheers, > George I have looked into the patches and vk source while reading/replying this email. I hope to do a proper review soon. toso _______________________________________________ grilo-list mailing list grilo-list@gnome.org https://mail.gnome.org/mailman/listinfo/grilo-list