Here is a chat transcript from yesterday that has some good information
in it. Some people don't like having transcripts sent out, but in this
case the dialog was really good and I wanted to keep this in an email
thread for the record...
<buck> i just sent an email to the rhq devel list regarding the multi
click issues, a possible shorter term solution with longer term value
<stefan_n> rbuck, there is one problem with your assumption from the email
<stefan_n> rbuck, while the UI is single threaded you can send multiple
async requests at the same time ... which basically makes the app
multi-threaded
<rbuck> stefan_n, yes, which is why i suggested using atomic set, cas,
get operations
<stefan_n> rbuck, you never know which response you will get back first
... if one rpc is faster than the other then you might get first the
response from the last request
<rbuck> stefan_n, true, vy true; this is okay so long as you maintain
the notion of view consistency
<rbuck> stefan_n, and yes, you cannot guarantee that A1 arrives before A2
<stefan_n> piping requests one by one gets the app back to the old
postback style ...
<rbuck> stefan_n, no serial requests here, can be async, parallel
<rbuck> stefan_n, the "view" is defined by the uuid, if the
response/request does not match, you don't render/send
<rbuck> it's just like transactions, all a transaction is, is a
logically identified set of related events, and the events themselves
can occur out of order; but if the transaction is cancelled, and a new
one begun, all that matters is the new txn id - that is your new view.
<rbuck> all the activities within the new view can be rendered in any
order defined by respones arrival time, but state from a stale view is
not permitted in.
<stefan_n> this can work great but I still see one pitfall ...
<rbuck> okay?
<rbuck> (that's why we're here)
<stefan_n> is it possible for one user initiated request to update two
components? the one that initiates the request and another one in a
separate part of the application
<stefan_n> in general in an MVC (MVP) architecture you have multiple
parts of the app consuming the data from a single user initiated request
<rbuck> yes, A = {A1, A2, A3}
<rbuck> A1-3 represent different components
<stefan_n> for example, when you ask for the user roles, you might
update two or three components based on the response
<rbuck> yes
<stefan_n> and they might not be nested ...
<stefan_n> how would your proposal handle this cases?
<stefan_n> and I am not sure if this applicable for rhq ...
<rbuck> but something has to initiate it all, a key-press or a mouse-click?
<stefan_n> correct ....
<rbuck> and that event is what is modified to atomically set the uuid
<stefan_n> but see the fact that you mouse-click again does not
invalidate the response you received for some other components ...
<rbuck> but... what you are saying is that we (today) have no way to
causally relate events? or do we? i would hope the latter for stability
reasons
<stefan_n> so if you have 3 components that depend on that response it
might be ok for 2 of them to still render this stale response
<rbuck> if A1, A2 render, but A3 is still outstanding, all is good, its
just slow
<rbuck> you cant distinguish a partial view caused by a network
partition from slowness or bug
<rbuck> A1+A2 is valid only so long as UUID(A) is current
<rbuck> so A1+A2 is not stale
<stefan_n> rbuck, we probably need input from the UI folks :)
<rbuck> yeah, i tried to enlist feed back by drawing their attention to
this list from the other list, or perhaps they will respond by email
<rbuck> re: stefan_n> but see the fact that you mouse-click again does
not invalidate the response you received for some other components ..
<rbuck> stefan_n, a second event invalidates all prior events, a new
view has to be rendered, and therefore prior events can be dropped; B
(and later) cause all A responses to be dropped
<ips> stefan_n: i don't think we currently have any places where more
than one view is listening for an rpc response
<ips> almost all of our callbacks are implemented as anon inner classes
<stefan_n> ips, so how do you aggregate views?
<stefan_n> ips, when the response comes you push it down to the child
components manually?
<ips> well for the resource detail view for example, there's a
ResourceTopView instance that contains a ResourceTreeView and a
ResourceDetailView
<stefan_n> GWT advocates event buses instead of manual component aggregation
<ips> the subviews basically make their own rpc calls to load whatever
data they require
<ips> stefan_n: yeah, i know. but coregui doesn't currently use any MVP
<ips> no event bugs, no spearation of views and presenters
<stefan_n> so if you have three panel that display the username and the
user name changes then you fire the event for changing user names and
that is distributed to all the components
<ips> eventbus
<stefan_n> I have used in the past an event bus with MVC
<stefan_n> the reference implementation for MVP has one
<ips> right, the guy who detects the username change would notify other
views
<ips> or the other views could periodically poll
<stefan_n> rbuck, we need to make sure your proposal does not box us out
of MVP/MVC and the concept of an event bus
<stefan_n> ips, with the event bus, the component that detects the user
name change just publishes an event on the bus ... no need to know who
the other views are
<ips> i agree refactoring the whole app to use MVP would be the best way
to go
<ips> but that's a major effort and it sounds like we don't have the
bandwidth to devote to it, at least not pre jon 3.0
<ips> rbuck's proposal could be a good stopgap solution since it doesn't
require changing a ton of classes
<stefan_n> ips, agree that it is a major effort
<ips> though as i suggested in my reply to his mail, i think you'd have
to update all the individual view classes if you wanted to be able to
drop outbound calls
<ips> however, i think it may be enough to just drop inbound responses.
<ccrouch> (11:45:39 AM) ips: however, i think it may be enough to just
drop inbound responses.
<ccrouch> thats exactly what I was just thinking :-)
<ccrouch> ips: if we only have a rendering problem
<ips> the server might end up doing a bit of extra work, but it wouldn't
hurt anything
<ccrouch> then that should be enough
<ccrouch> extra work is fine, as long is it doesnt get the backend in an
inconsistent state
<ccrouch> I guess the scenario for that would if you hit submit on
editing a form and then hit cancel? I'm not sure
* ccrouch stops guessing wildly
<ips> ccrouch: yeah, granted the call could be one that modifies
something (eg - updateRole()), but i don't think you'd necessarily want
to cancel that anyway. if that got called, then the user must have
clicked the Save button on the edit role page
<ccrouch> yep
<ccrouch> also 99+ of the cases we're talking about here do not modify data
<ccrouch> rbuck: thoughts ^ ?
<ips> agreed
<rbuck> event bus... that is just a transport in my mind, orthogonal
<ccrouch> rbuck: I meant about avoiding dropping outbound requests
<ccrouch> i guess in your email you describe it as an "opportunity to
optimize ", so not required
<rbuck> yes, in my email i describe this, and what i meant to say was
that outbound request drops are an optimization to avoid further
*unnecessary* work by the backend
<rbuck> if the outbound were feasible, that would be a nice to have, not
essential because the inbound comparisons would filter out stale view
information
<ccrouch> rbuck: "There are two types of requests that occur,
user-initiated and non-user-initiated."
<ccrouch> i dont see in your email where you address non-user-initiated?
<rbuck> we could keep [outbound filtering' that feather in our cap in
case we want to add it later
<ccrouch> is that relevant?
<rbuck> maybe? what i wanted to do is ask more about that?
<rbuck> what sort of non-user initiated actions exist?
<ips> it seems the only place we could distinguish would be in the click
handler for the link
<rbuck> ips, is there any place where a keyboard handler exists? assume
that ENTER-key is one we look for?
<ips> so we'd have to update all click handlers to update the uuid
<rbuck> (for data entry POST)
<rbuck> we should create a gateway-function somewhere that everyone
calls, but yes.
<ips> yeah, you can define global keyboard or mouse event handlers, but
i think it might be tough at such a low level to distinguish which
clicks or enter presses warrant updating the uuid and which don't
<ips> rbuck: fyi, there's an example of such a native handler in CoreGUI
<ips> onPreviewNativeEvent()
<ips> rbuck: besides a util function, we could also define some event
handler base classes that define an onClick() method that updates the uuid
<mazz> couldn't you just let requests go untouched if they don't have a
reuqest ID associated with it?
<ips> eg - NavigationClickHandler
<ips> mazz - yeah, that's how i was picturing non-user-initiated
requests would be handled
<ips> hmm, another possiblity would be to inser the uuid in our history
event handler, since all user-initiated requests funnel through that
<rbuck> mazz, non-user initiated, yes, or tag them ALL with a special
UUID, or simply "0"
<ips> insert
<rbuck> mazz, then they all go through the same code paths rather than
bifurcating the code
<rbuck> i think we're all basically saying the same thing there
<ips> CoreGUI.onValueChange()
<ips> that may be the way to go...
<rbuck> could someone provide me an example of a non-user initiated event?
<rbuck> just curious, but assuming there must be some out there
<ips> rbuck, we periodically poll the server for new alerts
<ips> and update the little alert count that's always shown near the top
in the gui
<rbuck> ips, thank you
<rbuck> so ips, for the non-user-initiated, would it make better sense
to simply use the current uuid?
<ips> i think it might work ok to just not set any uuid header
<ips> ie - i don't have a uuid, which means i'm not associated with a
particular user navigation
<ips> which tells the response handler to never drop such responses
<rbuck> perhaps,
<rbuck> upsides, incrementally add in the feature w/o breaking anything
<rbuck> downsides, code bifurcation on the server side and response
handler side?
<rbuck> (code bifurcation, i mean a branch in the code to handle the
"else" case where no id is provided; i tend to favor avoiding
unnecessary bifurcation to keep logic simpler more consistent)
<ips> wouldn't yo still need an else to do the if
(uuid.equals(NON_USER_REQUEST_UUID)) check ?
<rbuck> if the non-user event initiation logic always did "
send-async(make-request(get-current-uuid())) "
<rbuck> then the recv side is simply " handle(handler,
uuid.equals(get-current-uuid()), req)
<rbuck> something like that, hopefully you get the meaning
<ips> i would think non-user calls would do:
send-async(make-request(get-special-non-user-uuid()))
<ips> because get-current-uuid() would return the uuid for the current
user-inititated request right?
<rbuck> that's possible too, but only so long as for all non-user
initiated events E, none introduce inconsistent state WRT the current
outstanding event E
<rbuck> so, a question to help me...
<rbuck> if send-async(make-request(get-current-uuid())) were used for
non-user initiated, what would the consequences be?
<rbuck> ips, certainly some new alerts would be dropped
<rbuck> but later ones rev'd
<rbuck> its a trade-off i suppose? or does it actually violate a rule or
design intent by dropping occasional alerts?
<ccrouch> so btw
<ccrouch> if the only non-user request are the alerts
<ccrouch> I'm totally comfortable dropping alert notifications from the
menu bar, if it provides for a significantly more stable UI
<ips-lunch> i don't think dropping an alert count response every so
often would be a big deal, but i think what we're really interested in
tracking is user-initiated navigation requests
<ips-lunch> so i think i'd opt for only doing the uuid thing for those
<ips> and i think the history event handler will be a perfect spot to
generate the uuid and update the global var
<ips> not only clicked links funnel through there, but also the user
changing the url in the browser address bar
<rbuck> ips, so long as non-user-initiated events are _always_ rendered,
this should be fine. if a non-user-initiated event is ever added that is
only rendered on particular pages, and if the user moved to a page that
was not meant to render the content, what would happen?
<rbuck> ips, apart from this one question, it all sounds good to me.
<ips> rbuck: good point
<ips> i'm not sure if there are any case on non-user initiated calls
that only occur on certain views
<ips> well, auto-refresh of portlets would be one ....
<ips> those would only occur while the user was currently on one of the
dashboard views
<rbuck> if there are some cases, it would seem to favor always using the
current uuid, then you'd always be safe, even when things change
<ips> but i think we could just lump those into the same outer request
used for the dashboard view
<rbuck> (new code changes)