Jump to content

Variant vs Native Datatypes


Recommended Posts

Actually I was thinking about debugging and maintenance more than writing the code to launch the vi. Personally I find it a pain to debug code when dynamically launched vis are used.

I haven't found them to be a pain, as long as my method of using them has a way of opening their block diagrams, and has them shutdown cleanly. I've found "Shared Clones" VI's harder to debug than dynamically-launch ones.

That's exactly what I do when using LapDog.Messaging. (At least the technique is the same. Updating front panel controls may or may not occur in the user input loop.)

Have you considered wrapping the User Event primitives in a LapDog class, and adding a parent class of both it and the Queue class? If you put the "send message" methods in the parent, it would allow sending processes to treat the two types generically. This would probably not be that useful to you, if you only use User Events between loops on the same block diagram, but it might be an advantage to other LapDog Users that might code differently.

-- James

Link to comment

You mentioned a state message is rendered obsolete in a few seconds and would like the old message replaced in the queue by the new message. If I may ask a pointed question, why do allow messages--which should be propogated immediately through the application--to sit around in the queue for several seconds unprocessed? What can you do to improve your application's response time to messages? When I asked myself those (and other) questions I ended up with the hierarchical messaging architecture.

I've been thinking about your pointed question, and, though I agree with you about the importance of servicing a message in a queue promptly, I wonder if an alternate valid answer is "sometimes one doesn't need a queue, one needs a notifier." A notifier is like a queue where only the last element matters; perhaps the message-object extension of a notifier is something that returns the last message of each type/label/name. Like the notifier primitive, this might have use cases where it is preferable to a queue. I'll have to think some more about it, and perhaps experiment with adding such a message notifier to my messaging library.

-- James

Link to comment

Have you considered wrapping the User Event primitives in a LapDog class, and adding a parent class of both it and the Queue class? If you put the "send message" methods in the parent, it would allow sending processes to treat the two types generically. This would probably not be that useful to you, if you only use User Events between loops on the same block diagram, but it might be an advantage to other LapDog Users that might code differently.

I've thought about it a bit in the past but don't really like it for the following reasons. As before, you may or may not view these reasons as sufficiently valid for me deciding not to do it.

I don't see much point in creating a common parent unless I'm going to be using that parent in the code somewhere. A common parent (I'll call it "Postman" for lack of a better term) that abstracts the underlying message passing technology (queue, notifier, user event, etc.) would be very small. The Postman class can only expose those methods that are common to all child classes--essentially, "SendMsg" and "Destroy." But, if we look a little closer at SendMsg we see that the queue's SendMsg implementation has multiple optional arguments whereas the others have none. That means Postman.SendMsg won't have any optional arguments either and I've removed useful functionality from the queue class.

Two classes inheriting from a common parent should share a lot of common behavior. Events and queues share very little common behavior. True, they both can be used to send messages in an abstract sense, but that's about it. They offer very different levels of control (i.e. have different behavior.)

(True story: Several years ago when I first jumped into LVOOP with both feet I thought (as most OOP newbies do) that inheritance is the answer to most problems. I was working on a project that handled many different instruments and figured (again, as most OOP newbies do) I'd have them all inherit from a common Instrument class. After all, I have to Open and Close all of them, right?

The further I got into that project the more difficult it became to work all the functionality I needed into each instrument's Open method. On top of that, I didn't actually use Instrument.Open anywhere. The class heirarchy was an artificial constraint I had placed on myself and was contributing nothing useful to the project.)

Another reason for using inheritance is to make it easy to change functionality. For example, I'd like the PriorityQueue to be a child of the MessageQueue class. That would make it very easy for LapDog users to start a project with a MessageQueue and--if they get to a point where the project requires it--replace it with a priority queue by simply changing the constructor. Queues and user events are not interchangable by any stretch of the imagination, so there's no benefit there.

All in all, the "convenience" of having a single SendMsg method that works for both user events and queues simply isn't worth it.

I've been thinking about your pointed question, and, though I agree with you about the importance of servicing a message in a queue promptly, I wonder if an alternate valid answer is "sometimes one doesn't need a queue, one needs a notifier."

Wrapping notifiers makes more sense to me from an overall api standpoint than wrapping user events and I've considered that somewhat more seriously. I haven't done it yet mostly because I haven't needed it. Queues can do a remarkably good job of duplicating Notifier functionality. There are two main things that differentiate notifiers from queues:

1. Notifiers only store the last message sent. Easily replicated using a single element lossy queue.

2. Notifiers send message copies to each receiver. (One-to-many.) Generally in my code when messages need to be copied it is done by the component that 'knows' multiple copies are needed. For example, the mediator loop of the component that 'owns' the two receiving loops copies the message when it is received and sends a copy to each loop.

I view notifiers and user events sort of like specialized queues to be used in specific situations. I find notifiers most useful when a producer generates data faster than a consumer can consume it and you don't want to throttle the producer. (i.e. You want the consumer to run as fast as possible or you have multiple consumers that consume data at different rates.) Unless those situations are present queues are more flexible, more robust, and imo (though many disagree with me) preferable to either of those for general purpose messaging.

One thing that is important (imo) for robustness is that each loop in your application have only a single mechanism for receiving messages. (Nesting say, a user event structure inside a QSM's Idle case, can easily lead to subtle timing issues that don't show up in testing.) If you have a loop that sometimes needs queue-style messages and sometimes needs notifier-style messages, that's a good indication your loop is trying to do too much. Break it out into one loop with a queue and another loop with a notifier.

perhaps the message-object extension of a notifier is something that returns the last message of each type/label/name. Like the notifier primitive, this might have use cases where it is preferable to a queue.

One possible implementation for this kind of object is to create an array of notifiers, one for each message. It would have to have a separate table to keep track of the dequeue ordering. Another possible implementation is to use a single queue and every time a message is enqueued it iterates through the queue and replaces the previous message with the same name. A third implementation is to use a hash table. Regardless of the implementation, there are design decisions I'd have to make that end up reducing the overall usefulness for a subset of the potential users.

-When a message is replaced, should the replacement message go the rear of the queue or hold its current position?

-Since we're going to be searching and replacing data, the data structure used to store the messages and the search algorithms are going to have an impact on performance. Should I favor speed in exchange for using more memory (hash tables) or should I strive for a minimum memory footprint (array) and take the performance hit?

-Should I do the searching and replacing in the Enqueue method or Dequeue method? In other words, which loop should take the performance hit for the added functionality?

There are no absolute answers to these questions. It's up to the developer to weigh the tradeoffs and make the correct decision based on the specific project requirements, not me.

------------------

[Edit]

BTW, even though I've rejected nearly all your suggestions I do appreciate them. It challenges my thinking and forces me to justify the decisions I've made. I hope my responses are useful to others, but the act of composing responses has led to several "a ha" moments for me.

  • Like 1
Link to comment
I don't see much point in creating a common parent unless I'm going to be using that parent in the code somewhere. A common parent (I'll call it "Postman" for lack of a better term) that abstracts the underlying message passing technology (queue, notifier, user event, etc.) would be very small. The Postman class can only expose those methods that are common to all child classes--essentially, "SendMsg" and "Destroy." But, if we look a little closer at SendMsg we see that the queue's SendMsg implementation has multiple optional arguments whereas the others have none. That means Postman.SendMsg won't have any optional arguments either and I've removed useful functionality from the queue class.

Two classes inheriting from a common parent should share a lot of common behavior. Events and queues share very little common behavior. True, they both can be used to send messages in an abstract sense, but that's about it. They offer very different levels of control (i.e. have different behavior.)

Ah, but isn't "Send Message" the central functionality of a message sending system? Aren't these "extra levels of control", just like the "priority queue", not central to the business of messaging? "The ideal queueing system... spits things out in the order it received them..."; User Events do that.

Having a common parent class means one can write code with "plugin" communication methods. "Actor A" can register with "Actor B" for message updates without Actor B needing to depend on the exact communication method that A provided. Why does Actor B need to do anything other that "Send Message"?

Note that this does not mean throwing out the option to use the Queue advanced features; you can write Actor A to deal with a Queue explicitly (and use Queue methods like Queue.Preview or Queue.Flush) while Actor B just uses Postman.SendMsg. Or you could write both Actors to use Queues specifically. Having a parent class doesn't eliminate the ability to use the child classes fully, it just adds the ability to use them generically, using the common subset of functionality.

And to repeat the point, the one common function, "Send Message", is the only crucial one for a messaging system. In my own design of a messaging system, my parent class is actually called "Send", and it is used all over the place, particularly in implementing "Observer Pattern" designs.

All in all, the "convenience" of having a single SendMsg method that works for both user events and queues simply isn't worth it.

If not user events, what abut TCP? How would you extend LapDog to a network communication method, and wouldn't it be good to be able to take a previously-written design and just substitute a TCP_Postman for the Queue? Do you really need methods like TCP_Postman.Flush or TCP_Postman.Preview?

I view notifiers and user events sort of like specialized queues to be used in specific situations. I find notifiers most useful when a producer generates data faster than a consumer can consume it and you don't want to throttle the producer. (i.e. You want the consumer to run as fast as possible or you have multiple consumers that consume data at different rates.) Unless those situations are present queues are more flexible, more robust, and imo (though many disagree with me) preferable to either of those for general purpose messaging.

Well, I'm interested not in queues, but in message-passing methods, which is not exactly the same. For example, queues have the ability to have blocking enqueue (if a limited-size queue is full); but a blocking "SendMsg" is NOT acceptable in asynchronous messaging (and you can't have an effective "Observer Pattern" with a potentially blocking Observer.Notify). So I don't want full Queue flexibility. I want a selection of message-passing methods with some degree of plug-and-play interchangeability (on the sending side, at least).

One thing that is important (imo) for robustness is that each loop in your application have only a single mechanism for receiving messages. ... If you have a loop that sometimes needs queue-style messages and sometimes needs notifier-style messages, that's a good indication your loop is trying to do too much. Break it out into one loop with a queue and another loop with a notifier.

I'm not sure this is true for a notifier, which is less a method of receiving messages that must be acted on than a bulletin board of posted information that I can choose to look at if I need to (or ignore if I don't). A queue-based message handler could consult a notifier to get information needed to handle a message arriving on the queue. I envision querying my MessageNotifier for the last message of a specific name/label/type, but only when I need the information.

Regardless of the implementation, there are design decisions I'd have to make that end up reducing the overall usefulness for a subset of the potential users.

...

There are no absolute answers to these questions. It's up to the developer to weigh the tradeoffs and make the correct decision based on the specific project requirements, not me.

Can I not provide another tool in the toolkit because it might not be perfect for every job? Can't provide a hammer 'cause someone might prefer a slightly larger hammer? I certainly think I can come up with a reasonably good set of design choices in making a MessageNotifier that would be reasonably useful (assuming one really wants a notifier).

[bTW, I'm thinking "sorted array", "binary search", "in the enqueue method", and "exact order not important" (a notifier is a bulletin board, not an inbox).]

BTW, even though I've rejected nearly all your suggestions I do appreciate them. It challenges my thinking and forces me to justify the decisions I've made. I hope my responses are useful to others, but the act of composing responses has led to several "a ha" moments for me.

And I've gone back to using QSMs, but with better clarity of their pitfalls due to your reasoned arguments against them.

-- James

Link to comment

Well, I'm interested not in queues, but in message-passing methods...

I'm not sure this is true for a notifier, which is less a method of receiving messages that must be acted on than a bulletin board of posted information...

Queues, notifiers, network streams, globals, shared variables, etc. are all ways to get information (a message) from one part of your code to another (usually parallel) part of your code. They are simply transport mechanisms, each of which is tailored for specific situations. How the message is used (or even if it is used) is irrelevant to the transport mechanism.

I agree notifiers are generally not a good way to transport messages that must be acted on, but it is still a method of transmitting messages. That's why I use queues. That's not to say all messages received on the queue must be acted on. I frequently ignore messages (dequeue and do nothing) when the loop is in a state where the message is invalid.

I want a selection of message-passing methods with some degree of plug-and-play interchangeability (on the sending side, at least).

It appears this is one of our core differences. I'm not looking for plug-and-play interchangability between message transport mechanisms. It's an additional abstraction layer I haven't needed. I decide on the best transport mechanism at edit time based on on the design and purpose of the receiving loop. Most of the time a queue is the best/most flexible solution. Occasionally a notifier or user event is better for a specific loop.

Ah, but isn't "Send Message" the central functionality of a message sending system?

Yep, but you'll notice the class isn't named "Universal Message Transport." It's named "Message Queue." It's intended to be a fairly low level LVOOP replacement for string/variant queues with a few built-in messages to improve code clarity. There could also be a "Message Notifier," "Message Network Stream," etc.

If not user events, what abut TCP? How would you extend LapDog to a network communication method... Do you really need methods like TCP_Postman.Flush or TCP_Postman.Preview?

I'd build a light wrapper class for Network Streams (or straight TCP if I was so inclined) with methods specific to that transport mechanism, similar to what I've done with Queues. That class would not be a child of Message Queue.

...and wouldn't it be good to be able to take a previously-written design and just substitute a TCP_Postman for the Queue?

Nope. You're saying you want an abstraction layer that allows you to transparently replace,

CompA --QueuePostman--> CompB

with,

CompA --TcpPostman--> CompB

and have things just work. I don't think that is a realistic or desirable goal. There are different requirements and concerns for managing tcp connections than there are for managing a queue between parallel loops.

For example, suppose you're sending sensor data from one loop to another loop so it can be stored to disk. What happens when the link between the two loops breaks? Generally this isn't a concern using queues--you can verify it via inspection--so there's no point in writing a bunch of code to protect against it. But it is a big concern with network communication. How should the sender respond in that situation? In the sending loop SendMsg is inline with the rest of the processing code so it will (I think) block until connection is reestablished or times out. Do you really want your data acquisition loop blocked because the network failed?

My solution is to insert an abstraction layer when it is needed, going from this,

CompA --MsgQueue--> CompB

to this,

CompA --MsgQueue--> CompBProxy --Tcp--> CompAProxy --MsgQueue--> CompB.

Advantages: I'm not carrying around extra layers of abstraction in code that doesn't need it. (Reduced complexity) The purpose of each loop/component is clear and distinct. (Separation of Concerns) I don't have to change any of the code in CompA or CompB to implement it. (Open/Closed principle) I can implement special protection, such as saving the data locally, when network problems arise.

If I am ever faced with a situation where it's advantageous to handle different transports as a single type, I'll create a MessageTransport class and use composition/delegation to accomplish what I need.

Having a common parent class means one can write code with "plugin" communication methods. "Actor A" can register with "Actor B" for message updates without Actor B needing to depend on the exact communication method that A provided. Why does Actor B need to do anything other that "Send Message"?

Using the network example from above, *something* on ActorB/CompA's side of the tcp connection needs to store the data locally if the network fails. What's going to do that? TcpPostman?

And to repeat the point, the one common function, "Send Message", is the only crucial one for a messaging system.

Yep, right up to the point where you need one of the other functions. Then that one becomes crucial too.

"The ideal queueing system... spits things out in the order it received them..."; User Events do that.

Yes they do, but they don't provide the level of control that is sometimes needed with a message queue. It's rare that I need to preview or flush a message queue, but when I do having those abilities saves me a ton of rework that would be required to implement the same functionality. Plus there are other subtle issues with events that I don't have to worry about with queues. From a general purpose messaging standpoint, queues do everything user events do but do it better.

Can I not provide another tool in the toolkit because it might not be perfect for every job?

Absolutely you can, and I encourage you to do so... but it doesn't mean *I* should. :D

Link to comment

It appears this is one of our core differences. I'm not looking for plug-and-play interchangability between message transport mechanisms. It's an additional abstraction layer I haven't needed. I decide on the best transport mechanism at edit time based on on the design and purpose of the receiving loop. Most of the time a queue is the best/most flexible solution. Occasionally a notifier or user event is better for a specific loop.

Well, it just seems to me that this route leads to less reusability/flexibility, or alternately, a lot more work. A component written to communicate in one way can't be reused in an application where it must communicate another way without either modifying it or writing an entire Proxy for it. A custom Proxy may be more flexible that a generic solution, but 90% of the time the generic solution is fine.

For example, suppose you're sending sensor data from one loop to another loop so it can be stored to disk. What happens when the link between the two loops breaks? Generally this isn't a concern using queues--you can verify it via inspection--so there's no point in writing a bunch of code to protect against it. But it is a big concern with network communication. How should the sender respond in that situation? In the sending loop SendMsg is inline with the rest of the processing code so it will (I think) block until connection is reestablished or times out. Do you really want your data acquisition loop blocked because the network failed?

No. But not blocking and throwing an error is fine. I must admit that my network experience is only one project using Shared Variables, but the likely use I would have for networking is between Real-Time controllers and one or more PCs in the same room on a dedicated subnet. Network failure is rare, and in any case, there is little value in half my distributed application running if the network connection has failed.

Using the network example from above, *something* on ActorB/CompA's side of the tcp connection needs to store the data locally if the network fails. What's going to do that? TcpPostman?

They call that "message persistence" in the stuff on Message Queues like RabbitMQ. The reason I would like to wrap one of the open-source messaging designs is to get advanced functionality like this for free. I'm not sure I would have much use for it in typical applications of mine, though, so I would be happy with a TcpPostman with buffers at each end that errors out in the unlikely event that the buffers fill up.

Absolutely you can, and I encourage you to do so... but it doesn't mean *I* should. :D

If only my computer hadn't up and died on me last week! :( Gotta wait a couple of weeks for a new one. In the meantime I can keep bugging you. :D

-- James

Edited by drjdpowell
Link to comment

Well, it just seems to me that this route leads to less reusability/flexibility, or alternately, a lot more work.

I think it offers better reusability and less work. The time/complexity curve isn't linear--it's exponential. The best components are those that excel at a single task. They are easier to write, easier to test, and easier to read/use. It's harder to understand a single component doing two things than it is to understand two different components doing one thing each.

If your use cases are constrained enough to allow you to ignore the problems with Postman class hierarchy, that's great. As a reuse author I have to think about what others might need to do, not just what I need to do.

A component written to communicate in one way can't be reused in an application where it must communicate another way without either modifying it or writing an entire Proxy for it.

Writing a custom proxy isn't that big of a deal. Essentially you're just transferring messages between a queue and a tcp connection. Throw in whatever additional functionality you want, like persisting data to disk in case of network failure, and you're done. If you have a TcpConnection class and a TextPersistence class in your reuse library that encapsulate some of that boilerplate code, all the better.

A custom Proxy may be more flexible that a generic solution, but 90% of the time the generic solution is fine.

That may be true. What happens the other 10% of the time? As soon as you run into a situation where the generic solution doesn't work you're forced into clumsy workarounds that are unnecessarily complex. Additional changes are thrown on top of that, making the problem worse. I know 98% of the time when I write code before I need it (such as building persistence into TcpPostman) I get it wrong--usually because there are additional constraints or requirements I hadn't thought of.

If you find yourself getting tired of writing proxies, perhaps a better solution would be to create a reusable Proxy class encapsulating the common requirements. Then you can create a ComponentProxy class (possibly with Component or Proxy as the parent, depending on the situation) and extend/customize it for the specific need.

Network failure is rare, and in any case, there is little value in half *my* distributed application running if the network connection has failed.

This is the point I was making earlier with your use cases being limited. In your case it sounds like there is an implied requirement that if the app fails for some reason you can rerun the test. Would you change your opinion if your app was doing the data collection for a test that cost $2 million to run and a network failure caused lost data?

We all tend to be rather egocentric when it comes to implied requirements. No wonder, they're usually just subconciously assumed rather that stated. (If they were stated they'd be explicit requirements.) They influence our development patterns without us even knowing it. In my experience, they remain subconcious until we're forced into making a change that violates one. Then it smacks me in the face and taunts me. (*smack* "Whatcha gonna about it, huh? You can't touch me!" *smack*) I hate that.

The reason I would like to wrap one of the open-source messaging designs is to get advanced functionality like this for free.

Additional functionality is never free. It costs processor cycles, memory, hard disk space, and developer time while you (and future devs) learn the api. The costs may be acceptable to you and your employer, but it's not free.

In the meantime I can keep bugging you. :D

Yep, and I can keep saying no. :D

I understand how a Postman class would be desirable if you're using an architecture based completely on publish-subscribe. But publish-subscribe isn't always appropriate and I'm not willing to put constraints on MessageQueue (or any other message transports I create in the future) by making it a child of Postman in order to simplify that one use case.

Link to comment
If you find yourself getting tired of writing proxies, perhaps a better solution would be to create a reusable Proxy class encapsulating the common requirements. Then you can create a ComponentProxy class (possibly with Component or Proxy as the parent, depending on the situation) and extend/customize it for the specific need.

Can I encapsulate the local queues as well and call my Proxy class "Postman"? :D

I actually use my version of Postman ("Send") as the parent of classes that serve as Proxies for my Active Objects ("Parallel Process"). So I can extend/customize these proxies if needed. For example, my "MessageLogger" class (really a proxy of communication with a parallel-running VI that records messages) overrides "SendMessage" to collect additional information from the sending process:

post-18176-0-98172000-1314799885_thumb.png

The sending VI name and the message time seen above is extra functionality added by override.

So, I think I can add desired custom functionality in a non-clumsy way for that "other 10%" through inheriting off of Postman. And I like the Proxies being "up front"; my preferred abstraction is:

CompA --> CompB(proxy)

Which may explain why I'm uninterested in queue operations like "Flush" or "EnqueueInFront"; they don't mean anything in this abstraction.

-- James

Edited by drjdpowell
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.