-
Notifications
You must be signed in to change notification settings - Fork 358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wait for task no longer #9352
Wait for task no longer #9352
Conversation
@Fryguy This still stores the parameters in the session to be backwards compatible. Do you want to change 10 other controllers and remove this completely from the session? Pro: eradicates race condition and completely removes session values |
end | ||
|
||
browser_refresh_task(task_id, !!options[:flash]) | ||
session[:async][:params] = async_params |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need the ||= {}
up above to ensure this path exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good eye.
My question below is if we can drop session[:async]
all together.
In the meantime, I'll add it back.
session[:async][:interval] ||= 1000 # Default interval to 1 second | ||
session[:async][:params] ||= {} | ||
async_interval = params[:async_interval] || 1000 # Default interval to 1 second | ||
async_params = params[:async_params] || session[:async][:params] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I like the async_params coming in as a regular param - not sure why that is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is the whole solution.
Otherwise it is a last one in. in our case, it gets blown away by notifications
url hit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved over to Task
render :update do |page| | ||
page << javascript_prologue | ||
ajax_call = remote_function(:url => {:action => 'wait_for_task', :task_id => task_id}) | ||
page << "setTimeout(\"#{ajax_call}\", #{session[:async][:interval]});" | ||
ajax_call = remote_function(:url => {:action => 'wait_for_task', :task_id => task_id, :async_params => async_params, :async_interval => :async_interval}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh because of this ... hmmmm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async_parameters have been removed. now it only contains async_interval
da10933
to
f1886c5
Compare
ok, so we have a security problem just passing the action. The The action typically comes from params[:action] which is the url but those are protected. This new way passes action as a param (user editible) and that is not protected like it is when it comes in via the url. I can probably change these urls to have a single url with a blocker on a completed task_id (vs having a Aside: Also, these all use rjs. Would have been nice to drop that as well, but this is starting to get serious scope creep. |
WIP: moving parameters into the It is unfortunate that the task is created elsewhere - so we need to find and then update it |
f1886c5
to
d79e60d
Compare
update:
WIP: This PR is complete. First commit is from #9358 - after merging, can up-WIP |
d79e60d
to
2241435
Compare
update:
|
else # Task done | ||
session[:async][:params].each { |k, v| @_params[k] = v } # Merge in the original params and | ||
send(session.fetch_path(:async, :params, :action)) # call the orig. method | ||
async_params = task.context_data[:async_params] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we delete the async_params out of the task? They weren't there in the first place and we stuffed them in. My concern is that the subsequent handler of the task might take the context data and send it wholesale, for example, to some provider operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell, widgets and others store a bunch of data in context_data
. It looks like the wild west.
The only provider I see that even mentions context_data
is manageiq-providers-workflow that uses it to store the kubernetes id.
I don't see anything that uses context_data
whole sale. e.g. grep -r 'context_data[^\[.]'
is mostly assignments and nothing is accessed in a whole sale way.
I'm still open to removing it. Not sure if a glitch could cause issues here. We would delete it from the task when we view the task?
BottleneckEvent
does allow the full context to come out - but I didn't look too much into that since it seems irrelevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry - I misunderstood where this line of code fit in the flow. So, at this point the task is already completed, and it technically doesn't matter. I feel like it should be removed from a purity standpoint (i.e. whatever we add temporarily we shouild delete), but I won't hold up merge.
We would delete it from the task when we view the task?
No I was thinking just literally changing this line to
async_params = task.context_data[:async_params] | |
async_params = task.context_data.delete(:async_params).tap { task.save! } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. implemented but without the tap
dance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I merged before this landed cause I don't see it in the code.
render :update do |page| | ||
page << javascript_prologue | ||
ajax_call = remote_function(:url => {:action => 'wait_for_task', :task_id => task_id}) | ||
page << "setTimeout(\"#{ajax_call}\", #{session[:async][:interval]});" | ||
ajax_call = remote_function(:url => {:action => 'wait_for_task', :task_id => task_id, :async_interval => async_interval}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this as a parameter? I think this can be stuffed in the task along with the params. My concern is this is user modifiable, and then we generate JavaScript with it immediately afterwards. I'm not sure how it's exploitable, but it's easy to stuff random JavaScript into this parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is updated on every poll. I was thinking that it is unnecessary task churn.
This is the sleep time. Increasing it just changes the client polling. I guess you could increase it too high and the task would not be collected on the client.
But just closing the window would basically do the same.
If they lower the value, then they could increase the polling - but on the same note, they could just keep hitting the url and not bother with our ui either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, my problem is they could change it to random javascript and it gets embedded on the next line:
page << "setTimeout(\"#{ajax_call}\", #{async_interval});"
While I can't come up with a practical hack that would use this, I figured why even allow the vector if it's a as simple as storing in the task alongside the a params.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ensured it is an integer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conversations have asked if this would be better stored in the task itself. We are storing the parameters in there.
The ui process would be updating the task every second or so (1-5 seconds)
At the same time, the queue would hold this task open for a long time.
(As mentioned above I really don't like the churn)
But more importantly, this seems the recipe for a race condition - one process holding a stale copy of the object for minutes while another updates every second.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's a good point on the race condition, and I'm not sure if that table is set up for stale record detection. Ok this is fine then, especially if we can ensure it's an integer.
task.context_data_will_change! | ||
(task.context_data ||= {})[:async_params] = async_params | ||
task.save! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be more simply written as
task.context_data_will_change! | |
(task.context_data ||= {})[:async_params] = async_params | |
task.save! | |
task.context_data = task.context_data.merge(:async_params => async_params) | |
task.save! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, task.context_data_will_change!
is not necessary. Just have flashbacks from rails 3 (rails 4?) when it did not detect a change to a hash.
Also, task.context_data
is often nil
.
So,
task.context_data = (task.context_data || {}).merge(:async_params => async_params)
# or
asyc_hash = {:async_params => async_params}
task.context_data = task.context_data ? context_data.merge(async_hash) : async_hash
I kinda like the original way but, as long as we handle the nil
case, I'm good to change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally this one seems the cleanest. I thought context_data was automatically a Hash, but the initialization to en empty Hash is fine.
task.context_data = (task.context_data || {}).merge(:async_params => async_params)
2241435
to
45fbbea
Compare
update:
|
Merged #9358 |
update:
|
We were storing in session[:async][:params], but that ended up with a race condition and didn't allow multiple tabs
45fbbea
to
e7933d3
Compare
update:
|
update:
|
Backported to
|
Wait for task no longer (cherry picked from commit f42d416)
Overview
Depends upon
Issue
The
wait_for_task
mechanism relies upon the session.Every http request changes the session. So when multiple requests come in, updates to the session can overwrite the changes done by another request.
Example:
Host's display of Cap&U was breaking because hits to notifications were overwriting the session variables. Note: VM's display of Cap&U was working fine because it did not have a hit to notifications (and therefore didn't have this race condition).
Solution
Move
wait_for_task
parameters from the session to the url.Before
initiate_wait_for_task
setssession[:async][:params]
withaction
andparams{}
browser_refresh_task
puts shim into web pagewait_for_task
waiting for task to completesession[:async][:params]
is used to complete actionAfter
initiate_wait_for_task
gathersasync_params
browser_refresh_task
puts shim into web page withasync_params
wait_for_task
waiting for task to complete.async_params
) is used to complete action