01-24-2014 11:04 AM
Daklu wrote:
In my head all the logical threads an actor uses are encapsulated within the actor itself. Users aren't allowed to execute arbitrary code on the actor's thread (unless the actor exposes a message that specifically allows it, such as with plugins.) As an actor developer, I'm not sure how I can ensure an actor I have written will behave appropriately when users have the ability to add arbitrary computations to the thread my actor is executing on. Tradeoffs...
You, Daklu, have to worry about this aspect with general actors because general actor theory deals with cyclic graphs of actors. But the AF actors are a tree.
Let's name the author of the nested actor Nate and the caller actor Chris. Because the AF defines actors in a tree, I don't think it is Nate's job to make sure that the nested actor "behaves appropriately" in this instance. Nate has to make sure that the actor fulfills some contract and he publishes that contract. Chris, as author of the caller actor, also has to write an actor that fulfills some contract. Chris uses Bob's actor. The nested actor can only receive messages from within itself (which may include its own nested actors) and from the caller. Bob already ensured that the nested actor is consistent for all the internal messages. Chris' actor can send new messages to Bob's actor and know how the nested actor will be impacted. *Chris* is the one responsible for qualifying the nested actor's behavior in terms of his higher level of functionality. Anything Chris injects into Bob's thread is a well-defined adjustment to Bob's known operating contract.
Problems can arise if Chris decides to breach the encapsulation of the actor tree by sharing Bob's queue with some other actor. That makes the system as a whole cyclic. The system then has to start worrying about conflicting injections. That worry is contained at the level of the nearest common caller actor to both injector actors. As long as Chris has the ability to inspect and control changes to all modules up to the level of that common ancestor, he can theoretically analyze and assert the functionality of the system overall even if no mechanical theorem prover exists at this time that is capable of doing so (to the best of my knowledge).
There are many other ways for code to get injected into a nested actor if data in a message is anything other than just dead data. That means you have to rule out any message types that contain refnums, any classes, and any strings or variants that represent refnums or classes. If you aren't ruling those out, then there's already ways for code injection to happen, so you might as well design the system overall to account for that, and the actor tree provides that design accounting.
01-24-2014 12:47 PM
Daklu wrote:
"QSM" (neither your nor my definition) places no restrictions whatsoever on how data is shared between loops, message chaining, or message handling loops.
a) If the definition places no restrictions, then my statement that actors are QSMs is true.
b) But I suggest it places one rather critical restriction: Queue.
Daklu wrote:
Currently I'm leaning towards not liking it, since custom messages are primarily a convenience--they don't provide any functionality that users couldn't create on the own--and running arbitrary code on an actor's thread is an awfully big hole to drive bugs through.
They most certainly do create functionality that the *caller actor's author* could not create on his/her own. If you assume one user is the author of all the code, sure, you're right, but that's a very limited view of the modularity of modern systems. The caller may not have the ability to modify the nested actor code, and the nested actor author may not have accounted for all the atomic operations necessary for callers to have robust functionality. A trivial example is a hardware API that has a message for "Set Option A", "Set Option B" and "Set Option C". If a caller needs to change all three in one go, they're stuck -- there is no solution for them. Even wrapping an actor like that in another level won't help. You have to be able to hold the actor's execution lock.
As for the bugs, see my previous post about how the actor tree negates that issue.
01-24-2014 05:54 PM
AristosQueue wrote: Let's name the author of the nested actor Nate and the caller actor Chris. Because the AF defines actors in a tree, I don't think it is Nate's job to make sure that the nested actor "behaves appropriately" in this instance.
I wasn't even thinking about cyclic situations, but you're right that represents increased risk. The behavior I was specifically thinking of is the responsiveness of the message handling loop. When actors are built such that the time each messages spends waiting in the queue is negligible, the entire system is simplified. Allowing users to create custom messages that run on the actor's message handling thread introduces risk that the custom message will not be immediately read by the receiver.
But your explanation made me realize this is a new wrinkle on an old argument. The command pattern's nature makes it difficult to prevent users from writing custom messages that will execute on the actor's thread. (Especially since Labview doesn't allow developers to mark as class as 'not inheritable.') So if preventing that is too hard, might as well embrace it and let users figure out ways it can be useful.
I'm not saying it's a bad idea, just noting that it increases the risk that the actor will misbehave and puts more responsiblity on the developer writing the custom message.
(BTW, get NateBob to a psychiatrist. He's having an identity crisis.)
AristosQueue wrote: a) If the definition places no restrictions, then my statement that actors are QSMs is true.
Not quite. If your definition of QSM is the commonly understood one, then yes, you can say Actor Framework actors are QSMs. You still cannot say actors in general are QSMs, which is the claim I've been defending.
However, although my definition of a QSM does not place any restrictions on interloop communication, message chains, etc., it specifically permits those practices that are not thread safe. (Again, because these are things people commonly do when implementing a QSM.) Actors do not allow inter-actor communication outside of messages, and they do not allow message chains. That is why I continue to assert an actor is not a QSM.
(And, even if your definition of QSM is correct, then although "AF actors are QSMs" would be true in some sense--similar to saying "I am a skeleton"--it would also be largely meaningless. It is nearly as informative to say, "AF actors are while loops.")
AristosQueue wrote: They most certainly do create functionality that the *caller actor's author* could not create on his/her own... A trivial example is a hardware API that has a message for "Set Option A", "Set Option B" and "Set Option C". If a caller needs to change all three in one go, they're stuck -- there is no solution for them. Even wrapping an actor like that in another level won't help. You have to be able to hold the actor's execution lock.
I concede that sending a single custom message that executes A, B, and C in order all at once may provide a small timing advantage over sending messages A, B, and C sequentially. However, I'd be surprised if that advantage has any practical effect in more than a very small fraction of applications written. (And if their timing requirements are that tight, the overhead of an actor-oriented system might not be the best solution.)
To all external actors, a wrapper actor (mediator, facade, adapter, etc.) that converts message ABC into separate messages A, B, and C is indistinguishable from sending a custom ABC message to the instrument actor. It is effectively an execution lock because all external actors must go through the wrapper, and the wrapper won't send new requests to the instrument actor until it is done sending A, B, and C.
The one way in which a wrapper is not identical to a custom message is if the instrument actor has subactors that can send messages that can change the settings of A, B, and C. Let's consider a few different scenarios:
Message sequence with a wrapping actor
Wrapper --> Device MHL: Set A=1
Wrapper --> Device MHL: Set B=2
Device Subactor --> Device MHL: <SomeMsg>
Wrapper --> Device MHL: Set C=3
Wrapper --> Device MHL: Take measurement
Equivalent sequence with a custom message (Msg from subactor delayed)
Wrapper --> Device MHL: Set A=1; Set B=2; Set C=3; Take Measurement;
Device Subactor --> Device MHL: <SomeMsg>
Scenario 1:
Suppose the device actor controls the hardware directly in the MHL, so the Set messages make changes to the it immediately. Further suppose the sub actor is monitoring some sensors for fault conditions, and if it detects any it immediately turns the device settings off and sets an error flag preventing users from taking a measurement until the error is cleared. The actor was designed this way to minimize the risk of costly damage that will occur if a fault condition is present when taking a measurement.
Thanks to the custom message written by a graduate student who didn't understand the intracies of the instrument actor, your expensive system has just burned out, the grant money dried up, tenure was denied, and you're standing on a freeway offramp with a cardboard sign that reads, "Will conduct experiment for food." Should the graduate student have better understood what the ramifications would be prior to creating that custom message? Absolutely. Should he have had any reason to believe his custom message could lead to that kind of failure? *shrug* I dunno... what do you tell your users about creating custom messages? Do you warn them it can be very dangerous?
Scenario 2:
In this scenario, let's assume the device actor is designed such that the actor's MHL manages information and filters messages, and the device itself is controlled in a helper loop. When the device detects a fault state, it sends a message to the MHL, which in turn sets a flag and responds to any Take Measurement requests with a 'device error' message until the device is reset. This is done because the instrument will still return measurement values if it is in a fault state, but it isn't obvious the data are invalid.
Again, the important message--a fault has occurred--is delayed, with the result that data is collected and assumed to be valid when in fact they are not.
Scenario 3:
Let's say the device is controlled in the actor's MHL like in scenario 1, but this time instead of the subactor sending a fault message, occasionally it sends a "Set A=10" message. (Why? We don't know.) In this scenario the user is guaranteed to get a measurement while A=1, B=2, and C=3, which they wouldn't be able to do without a custom message. Is this sufficient justification for custom messages? In my opinion, no. Honestly I think this is a very questionable design, as external actors are competing with internal subactors for the shared resource. The custom message isn't a solution; it's a band-aid that a developer can use and hope everything works right.
Summary
I think custom messages running in an actor's MHL could be used to build some very creative stuff that isn't possible in models that don't allow it. Honestly though, I don't see execution locking as a use case with much value beyond convenience (requires less code) or a band-aid (work-around to poor design or actor abuse). And to me, the risks of making a critical mistake are high enough that I would exhaust all other options before going down that path.
01-25-2014 08:40 AM
@Daklu wrote:
@AristosQueue (NI) wrote:They most certainly do create functionality that the *caller actor's author* could not create on his/her own... A trivial example is a hardware API that has a message for "Set Option A", "Set Option B" and "Set Option C". If a caller needs to change all three in one go, they're stuck -- there is no solution for them. Even wrapping an actor like that in another level won't help. You have to be able to hold the actor's execution lock.
I concede that sending a single custom message that executes A, B, and C in order all at once may provide a small timing advantage over sending messages A, B, and C sequentially. However, I'd be surprised if that advantage has any practical effect in more than a very small fraction of applications written. (And if their timing requirements are that tight, the overhead of an actor-oriented system might not be the best solution.)
To all external actors, a wrapper actor (mediator, facade, adapter, etc.) that converts message ABC into separate messages A, B, and C is indistinguishable from sending a custom ABC message to the instrument actor. It is effectively an execution lock because all external actors must go through the wrapper, and the wrapper won't send new requests to the instrument actor until it is done sending A, B, and C.
I took AQ to mean that A,B and C must all change otherwise the Hardware will be in an invalid state (when just A, or A and B, are changed). Even if no external message can be acted upon in that invalid state, an internal action may happen and cause a fault. Locking up the "Actor" object in a custom message would prevent any internal messages executing on the "Actor" object.
To me, the hardware "actor" in this example is a poor actor design, in that it exposes public methods/messages SetA, SetB, and SetC that are capable of leaving the Actor in an invalid state. It should only expose SetABC.
01-25-2014 11:39 AM
drjdpowell wrote:
To me, the hardware "actor" in this example is a poor actor design, in that it exposes public methods/messages SetA, SetB, and SetC that are capable of leaving the Actor in an invalid state. It should only expose SetABC.
Not only poor actor design, but poor hardware design as well (assuming the hardware actor represents a single device.)
01-27-2014 01:55 PM
drjdpowell wrote:
To me, the hardware "actor" in this example is a poor actor design, in that it exposes public methods/messages SetA, SetB, and SetC that are capable of leaving the Actor in an invalid state. It should only expose SetABC.
Doesn't help. Suppose you write only SetABC. I will still find myself wanting a custom message for only Send A.
Suppose I have three properties: Minimum, Maximum and Value. Minimum cannot be greater than Maximum, and Value must always be between those two limits.
Compound properties that require all three be set every time make it a lot harder to write the UI code, for an easy example. I have a numeric control on the screen for "Minimum" and a separate for "Maximum". When one of these controls is modified by the user, I want to send a message with its new value. It's a pain to have to query the other one for its current value and build a composite message.
So I create a custom message that is just Set Minimum. When that message is received, it reads the current range, overwrites the minimum, and then writes the range -- all as a synchronous by-value operation.
Essentially, the Do.vi is/can be the controller layer of an MVC. And that makes the overall system simpler.
Daklu wrote:
I don't see execution locking as a use case with much value beyond convenience (requires less code) or a band-aid (work-around to poor design or actor abuse).
That to me is more than sufficient value. It is an absolute gem in real-world situations.
My favorite example is
* set minimum
* set maximum
* set value
* set range (min and max as a pair)
* set range and value
If the author of the class is reaaaaaaally forward thinking when building a by-ref architecture, he will build all five of these. But more commonly, only the first three of these will be exposed. That's the nature of programmers. Minimum returns an error if it is greater than the current Maximum; value returns an error if it is out of range. That is a more than sufficient API for the by-value class, and when it is by-value, it can easily be wrapped in a helper function for setting both ("If newmin > curmax then set max first else set min first. Then set value."). People who think of the by-ref case as anything special are very rare until long later when it bites them.
Daklu wrote:
(And if their timing requirements are that tight, the overhead of an actor-oriented system might not be the best solution.)
It isn't the timing requirements (i.e. hitting a particular timing schedule) that I'm worried about. It's the functionality requirements (i.e. never entering a bad state).
Daklu wrote:
Wrapper --> Device MHL: Set A=1; Set B=2; Set C=3; Take Measurement;
Device Subactor --> Device MHL: <SomeMsg>
...
Thanks to the custom message written by a graduate student who didn't understand the intracies of the instrument actor, your expensive system has just burned out, the grant money dried up, tenure was denied, and you're standing on a freeway offramp with a cardboard sign that reads, "Will conduct experiment for food."
If any one of those four messages takes a long time to execute, then any one of them could have caused the burn out on its own and the *handling* of such messages would need to have turned into a state handling system within the actor itself. In other words, if this happens, the burn out is NOT the grad student's fault. It is the author of the original actor's fault for making that handling synchronous. If it takes 20 minutes to execute Set B=2, that should have been turned into a "post a message to my actor core to begin moving to 2" and then get the synchronous code out of the way.
01-27-2014 04:47 PM
AristosQueue wrote:
drjdpowell wrote:
To me, the hardware "actor" in this example is a poor actor design, in that it exposes public methods/messages SetA, SetB, and SetC that are capable of leaving the Actor in an invalid state. It should only expose SetABC.
Doesn't help. Suppose you write only SetABC. I will still find myself wanting a custom message for only Send A.
...
Essentially, the Do.vi is/can be the controller layer of an MVC. And that makes the overall system simpler.
That sounds much better. I can certainly see the value in custom Do methods, but wonder if it will be used as an excuse to not to write the actors "API" properly. An actor's API should be robust, and always leave the actor in a consistent state, regardless of how it is used. I like to use the analogy of the ACID principle of databases (well, not the D for durability part). The C is for consistency: no transaction can leave the database in a state inconsistent (with its internal rules, such as Max>Min). The A is for atomicity: transactions work completely or not at all. SetABC can be written to hold to these rules even if you use it to just set A. Individual SetA, SetB and SetC messages could be written to hold to these rules, but there is a danger that the designer will rely on them only being called in a certain way (in the messages that the actor designer creates), a way that may not be understood by another designer writing custom messages for using the actor. That's why I wondered (in another thread) how many users of the Actor Framework appreciate that it is the actor class public methods, not the messages, that define the actor's API.
01-27-2014 06:02 PM
There is a difference between leaving the actor in an inconsistent internal state and leaving it in an undesirable external state. I may have a controller whose API lets me set min or max to voltages far apart from each other, but for this particular application, I never want them more than 5 volts apart. I will build the necessary API wrappers to account for that. Perhaps set minimum and set maximum each, respectively, check for voltage being in that range. But I may want to create a custom message that will slowly step the whole range up from 5-10 up to 200-205 rather than me, the caller, having to generate that long stream of step-this messages.
drjdpowell wrote:
That's why I wondered (in another thread) how many users of the Actor Framework appreciate that it is the actor class public methods, not the messages, that define the actor's API.
I can't guess. I never considered it anything *other* than the public API, and I've built custom message classes in many demo I've done for folks. I know I have written many times, including I think in the AF documentation, variations along the lines of of, "Write your by-value class as a class first, define its public and protected APIs, then make it inherit from Actors in order to enable remote communication."
It was only MUCH later (like a couple years later) after I first started showing folks the AF that someone highlighted actor-oriented languages that were exclusively by-message APIs. Trying to say, "LabVIEW (implying AQ and AF) should adopt the terminology and conventions of those [standard?] languages" was a hard argument to make because those other languages generally didn't have a dataflow implementation for their actual function code (meaning they *thought* in terms of references) or they didn't have a non-actor inheritance system. I very much want users to be able to leverage their knowledge of OO to write better actors. My hope is that the design principles that we're teaching them for all the functional parts of their code are the exact same principles they can then apply to the actor parts of their code.
01-28-2014 04:11 AM
AristosQueue wrote:
There is a difference between leaving the actor in an inconsistent internal state and leaving it in an undesirable external state.
I agree. Your custom Do methods are a bit like database transactions. And that seems very powerful. But, the actor needs to be designed with this in mind. What if your SetMinimum method doesn't actually set the minimum, but instead records the desired new minimum and sets a "NeedsUpdate" flag, with a periodic "DoUpdate" internally-generated message occurring once a second. Then your custom ramp-the-range message will fail, as you are blocking receit of "DoUpdate". Or what if there is an "EmergencySafteyShutdown" message that the actor must respond to?
...Actually, now I take the time to read it, Daklu's posts already pointed this out. And then you pointed out that a well-designed actor will have critical processes in helper loops or nested actors, and not depend on it's message-handling loop being unblocked. BUT, are developers actually doing this? Or are they using time-delayed send message (or whatever it's called in the AF) to schedule important tasks?
BTW, my own "actor"-like framework, "Messenging", also has a potential hole in it. My "enqueuer"-equivalent class has an overridable "Send" method, so child classes can be written that execute arbitrary code inside any actor that tries to send a message. I do not protect against this, and just assume Sends are "quick".
01-28-2014 12:47 PM
drjdpowell wrote:
BUT, are developers actually doing this? Or are they using time-delayed send message (or whatever it's called in the AF) to schedule important tasks?
If they aren't, I don't consider that a weakness of the AF. That would be a weakness in any API -- even just a regular class being passed around that happened to do operations on some piece of hardware. I believe it is easier to teach people "here's how you build a good API" and then teach them "here's how you make that API cleanly referenceable" than it is to say "here's how you build one API" and "here's a completely different way to build this other API".