Using parfeval to call eval function not working - why?

First of all, I know using eval at all is generally bad practice, but in this case it's kind of necessary. I have a piece of software where users (other programmers) need to be able to write their own functions to be called at a particular time. It includes a GUI where, before running the software, they can browse to the function that they want to run at a particular point in the process. The name of the function is saved to the GUI model and I use eval to make sure the correct function is run when the time comes. This software has been used for a couple years without issue so the eval aspect of this works just fine for our case.
Now I'm trying to update the software so that this function, that was generated by the user, is run on a parallel pool. An abstraction of my code would look like this:
P = parpool(1);
Q = parallel.pool.DataQueue;
afterEach(Q, @disp);
run_command = "test_func(Q)"; %This is a test function I created, normally this would contain a string with the user's function name and inputs
success = parfeval(P, @eval, 1, run_command);
I then have the function test_func defined as follows:
function test_func(Q)
send(Q, 1);
pause(2);
send(Q, 2);
pause(2);
send(Q, 3);
end
This code above does not display the numbers as expected. When I look at 'success', the FevalFuture, it shows an error message of:
"Incorrect use of '=' operator. Assign a value to a variable using '=' and compare values for equality using '=='." at line 39 of this file: C:\Program Files\MATLAB\R2021b\toolbox\parallel\cluster\+parallel\+internal\+queue\evaluateRequest.m.
I'm not sure if this file is part of the eval function or part of the parfeval function. So I guess I'm wondering why my original code which simply looked like
eval(run_command);
works just fine, but putting this into parfeval suddenly throws this random error?
If I change the code to pass test_func into parfeval directly, it works just fine.
P = parpool(1);
Q = parallel.pool.DataQueue;
afterEach(Q, @disp);
run_command = "test_func(Q)"; %This is a test function I created, normally this would contain a string with the user's function name and inputs
success = parfeval(P, @test_func, 1, Q);
So it seems like using @eval as the function passed to parfeval is causing some kind of issue. My question is why? Is this a combination that just doesn't work or is there something wrong with the way I am calling it?
Any insight would be appreciated, as I'm new to parallel computing. Thank you!

 Accepted Answer

Transparency violation. parfor and parfeval need to know the names of the functions at analysis time so that it can copy the code to the workers before evaluation starts.

8 Comments

So does this mean there is no way to run eval on a parallel pool? I thought in this case “eval” was the name of the function.
parfeval is not able to look at the run-time input run_command to figure out that it needs to transfer test_func to the worker. It is also not going to be able to analyze to see that it needs to copy Q to the worker.
It is forbidden to use eval directly inside parfor. I do not know if it is forbidden directly in parfeval.
It is not forbidden to have parfor or parfeval call a fixed function and the fixed function calls eval. But remember the workspace for the eval is going to be that function, not the client and not the direct worker. Also you might need to deliberately use the constructs to attach a file to the worker.... which implies that you have a limited list of permitted functions ahead of time.
I have also tried to create a function in my controller (the function calling parfeval is also in my controller class) called evaluate_run_function where I pass it the variable Q and it calls eval. So:
P = parpool(1);
Q = parallel.pool.DataQueue;
afterEach(Q, @disp);
run_command = "test_func(Q)"; %This is a test function I created, normally this would contain a string with the user's function name and inputs
success = parfeval(P, @evaluate_run_command, 1, run_command, Q);
function evaluate_run_command(run_command, Q)
eval(run_command);
end
However, I got exactly the same result with the same error. I thought this way, the workspace (being evaluate_run_command) would still have the necessary variables and so perhaps that would work, but it didn't. Do you think attaching the class file which contains this method to the worker would make a difference? As far as I can tell, it has access to evaluate_run_command it's just getting that error when it tries to run it.
Thank you for your insight by the way, this has been very helpful and given me a lot to think about.
While parfor has the workspace transparency restriction, there is no such restriction for parfeval. So, in principle, it's fine to use eval directly as the function called by parfeval.
The problem with your original code is indeed that the parfeval call doesn't know that it needs to transfer Q.
I think your evaluate_run_command approach should work - in fact, it works for me.
P = parpool("local", 1);
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 1).
Q = parallel.pool.DataQueue;
afterEach(Q, @disp);
run_command = "test_func(Q)";
success = parfeval(P, @evaluate_run_command, 1, run_command, Q);
fetchOutputs(success)
ans = logical
1
%%
function test_func(Q)
send(Q, 1);
pause(1);
send(Q, 2);
end
%%
function ok = evaluate_run_command(run_command, Q)
eval(run_command);
ok = true
end
(Unfortunately the DataQueue disp statements don't make it through to the output in this environment, so I added a simple "success" flag to evaluate_run_command)
I'm a bit confused by the original error you're seeing. That looks as though something has gone wrong internally. I can't reproduce it here though (even in R2021b).
As to whether the workers will be able to find the function specified by the user - for "local" workers, there shouldn't be a problem (providing the function is on the "client" path). For remote workers, parfeval has an error recovery mechanism that tries to attach any function that it thinks the workers are missing, regardless of what the original parfeval function was. In other words, even with remote workers, things might just work out OK. If you want to be sure though, you can use addAttachedFiles to attach things explicitly.
@Edric Ellis Thank you so much for your comment, this was really helpful! After going through my code again, I realized the evaluate_run_command method does actually work - I had just not refreshed my object after changing the code or something. The internal error only happens with the first method I tried.
I have another question with it though perhaps you'll know the answer to, if you don't mind. Doing it this way, the afterEach function does not execute, because the workspace now where data is being sent back to is the evaluate_run_command function, not the original function from which I'm calling parfeval. I actually really need the afterEach function to run correctly. In real life, this will not just be displaying numbers for debugging purposes. Every time the user's function sends back data over Q, the client will run a function to update a GUI being displayed on the user's screen, using afterEach and the data sent back from the worker. So I need that disp (or whatever function is in place of it) to work. Does that mean this method, of moving eval into a different function like evaluate_run_command, won't actually work for my purposes?
I tried moving the afterEach into evaluate_run_command but it does not work, I get a pretty clear error that says afterEach must be in the same workspace that created the queue. I tried moving both the queue creation and afterEach lines into evaluate_run_command, but while the function ran without error, the numbers were still not displayed.
You said in principle the original code, calling eval directly in parfeval should work. Is there some way I can alter my code to pass Q into parfeval as well as eval's input?
When I try the code I posted outside the "MATLAB Answers" environment, my afterEach call does execute correctly, so something else must be going wrong. I'm not sure quite what you mean by "the workspace now where the data is being sent back to is the evaluate_run_command function"... If you try the code exactly as I posted, does that not work for you? The data received by a DataQueue doesn't really end up in any specific "workspace" as such. The data simply gets posted in to the function that you specify via afterEach.
You definitely do not want to call afterEach on the worker. It needs to run on the client exactly as you have it in the original code.
I don't think it's possible to use parfeval directly with an eval expression that needs input data to be passed. That's basically because eval itself takes only a single input - the command text. There's no place to put any other data. (The effect that you want is to populate the workspace and then evaluate an expression - eval can't do that, which is why you need a wrapper function like evaluate_run_command).
One other option you might wish to consider is converting the user function name into a function_handle using str2func. This might simplify things a bit.
@Edric Ellis Sorry for being unclear, I may just not understand correctly how fetchOutputs works. Your code does work when I run it as you posted, but you have to use the fetchOutputs function to actually see the display on the screen. And what it displays is not the results of each disp function, but the result of "ok" set in the wrpaper function. Doesn't this mean that you are fetching the outputs and displaying them all at once, at the end, after the full evaluate_run_command is done running on the worker? And that the outputs in test_func themselves are never being displayed?
In real life, the function I am running on the worker takes about 20-30 minutes to run (depending on how the user has customized it). It will be returning data over Q every few seconds, and that data needs to be used immediately to update a GUI displayed on the screen (the update_gui function called by afterEach). It would not work for my purposes if its outputs (which are not explicitly returned outputs, but are simply changes displayed on the GUI) cannot be seen until the entire parfeval function is done running.
I also wasn't sure that fetchOutputs would even work for my real project, since the update_gui function being run with afterEach does not explicitly return any outputs. It simply updates the gui's model and uses drawnow to update the gui objects. I guess this may not be an issue since disp does not explicitly return an output either?
I will look into str2func, I don't think I've used that one before. It may have application elsewhere even if it doesn't help here. Thank you again for your help!
@Edric Ellis After submitting the previous comment I changed the wrapper function to not return the "ok" variable, and test_func to have longer pauses in between displaying numbers, and it does actually display the 1 and 2 as the f unction executes, not all at the end. Rather than returning the logical 1 "ok" variable. So perhaps my concern is wrong and this will work! I'm only trying to understand this in my head, rather than just testing it out, because to test out the real software I have to schedule time in a lab on some specific hardware, I can only test it out with these test functions on my own computer. So sorry if I am making up problems that aren't there simply because I don't fully understand matlab parallel computing yet.

Sign in to comment.

More Answers (0)

Categories

Products

Release

R2021b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!