LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Dr. Damien's Development - Running Top Level VIs IV - A Tale of Two Servers

The Response queues were more complicated because

1) the Server (Active Object) has to talk to multiple clients

2) The Server may already be running (the Active Object may already exist) when the client attempts to "Open" a connection (hmmm create a

method-invokation facility, path? I don't know what to call it in LVOOP-speak. Do you?).

 

I handled issue #1 by using a queue created by each client, so each client has its own queue to receive responses (method data).

 

 

 

I'm in the middle of creating a couple active objects for a project right now.  My design is based largely on the work done by Kurt Friday over on Lava.  His solution uses DVRs so I had to back port it to 8.6.

 

You can eliminate the response queue altogether by simply making the "normal" class data (as opposed to active object framework data) ByRef instead of ByVal by putting it in an unnamed DataQueue.  Your methods that return values then can simply grab the latest data from that queue and return it to the client.  From the client side it's a simple as dropping a sub vi.  They don't have to worry about creating and injecting a response queue to receive information from active object.

 

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

 

Damien,

 

The [User Event] is used as part of the shutdown sequence for a large set of freely running VIs. An example would be a main application with a string server for localization, error server for error handling, and multiple non–modal windows/dialogs which may or many not be up at exit time.

 

Can you explain this a little bit more?  I've found that injecting user events into a module just overly complicates both the client code and the module code.  I don't see the advantage (and lots of disadvantages) of generating user events for each server as opposed to simply calling a shutdown vi.

 

 

The result is in the ServerTemplate.lvlib attached below. Note that the core code is still there and operates the same way. However, the data is now a safe global, so can be updated asynchronously. 

 

This also has the side effect of not allowing you to run multiple instances of the same server.  IMO the object state data is still useful for active objects even if you can't derive completely new functionality in a child class.

Message 21 of 37
(2,327 Views)

Daklu wrote:

The Response queues were more complicated because

1) the Server (Active Object) has to talk to multiple clients

2) The Server may already be running (the Active Object may already exist) when the client attempts to "Open" a connection (hmmm create a

method-invokation facility, path? I don't know what to call it in LVOOP-speak. Do you?).

 

I handled issue #1 by using a queue created by each client, so each client has its own queue to receive responses (method data).

 

 

 

I'm in the middle of creating a couple active objects for a project right now.  My design is based largely on the work done by Kurt Friday over on Lava.  His solution uses DVRs so I had to back port it to 8.6.

 

You can eliminate the response queue altogether by simply making the "normal" class data (as opposed to active object framework data) ByRef instead of ByVal by putting it in an unnamed DataQueue.  Your methods that return values then can simply grab the latest data from that queue and return it to the client.  From the client side it's a simple as dropping a sub vi.  They don't have to worry about creating and injecting a response queue to receive information from active object.

 

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

 ...

 

Hi Daklu,

 

Thanks for the reply!

 

First thought (tongue in cheek) Cheeter! Smiley Wink

 

AS silly as it sounds, I refuse to "look at the back of the book for the solution" unitl after I have figured it out myself. After I have something that satifies my needs I will let myself look at other methods.

 

That code is still being developed in LV 8.5 so I can't take advantage of the DVR. They would alos be a nightmare to try to port across Applications instance on a machine or between multiple machines. I have already done the thought exercise on porting queue between machines, so that seems like an easier connection path going forward.

 

The queues will help my offer serivinge multiple clients at the same time. In one manifestation of this active Object I'd like to implement an API for an ancient DAQ serial based DAQ system I buily and use for my model train layout. It has AI AO DI and DO with rather large channel count. I would like to model the API after what NI's DAQ stuff does so my Active Object (at least this one version) will serve as other AO's each of which will handle AI or AO, or....

 

This means I will have to maintain history buffers for each client. This "fan-out" of the data flow from servers to clients also suggests a queeu for each client.

 

But this is just a work in progress and I am still thinking about how i can accomplish all of the miracles I want it to do. In fact I was having trouble trying to figure out a nasty little detail that I had been ignoring. This is what I think may work (I will have to wait for this week-end to try it out).

 

When i develop child classes with over-rides VI that invoke the same Template but pass a Child class to define the nature of the Active Object, I was puzzled who I would pass Child class specific commands and recieve Child specific replies since Polymorphic Dynamic dispatch VIs are not supported. It hit me last night that if I convert my command and response queues over to classes then I can pass children of those Classes (children of the Command Class) and use over-ride VI of the Child class to test and cast as the appropriate types. Sorry if that doesn't make much sense yet.

 

So I am still working on my "home-work".

 

Thanks for putting up with my morning ramblings,

 

Ben

Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 22 of 37
(2,231 Views)

The key to using a user event for shutdown is to use only one user event.  Said event is passed to each server/dialog (there was only one in this example).  On shutdown, the calling code only needs to fire a single shutdown event and all the servers and dialogs respond to it.  The servers and dialogs should properly close themselves and clean up before exiting.  In this sort of situation, I usually send an acknowledgement from each server/dialog to the main code that shutdown is complete so that the main code will not exit before all the called code is finished (a single user event is useful for this, as well, data containing the name of the module shutting down).  This prevents problems with the code exiting before hardware is properly shut down or databases properly closed.  In an executable, LabVIEW will exit when the last top-level front panel closes, even if other code is running, so this is a real issue.

 

In the example, I also do most of the cleanup in auxiliary VIs, not the main server.  This does not need to be the case.  It is often easier to do all cleanup in the server itself before exit.

0 Kudos
Message 23 of 37
(2,217 Views)

Accessing data inside a server does indeed lead to race conditions.  It took me three tries to get this right with the FPGA Wizard, and the solution was more complex that I wanted it to be.  Reading data is not an issue.  Writing it is.  I distinguished between a simple read and a read-for-write.  The read-for-write locked the particular set of data being modified and supplied a key to unlock it.  The writer had to supply the key as well as the data to write.  I wrapped it all in an API to make it easier to use, but it still was not really good. I believe I ended up having to poll in one wait state.  If you are really interested, I can try to track the code down (it has been several years since I worked on it).  There are probably better ways to do this now, in any case.

 

You are really creating a database, at that point, and it may be easier to use a database, such as SQL.

 

 

0 Kudos
Message 24 of 37
(2,212 Views)

DFGray wrote:

The key to using a user event for shutdown is to use only one user event.  Said event is passed to each server/dialog (there was only one in this example). 


I still don't like user events as a solution because:

  1. By using a single user event the client doesn't have the resolution to shutdown a single server.  It's either all or nothing.  What happens when one of the servers gets into an error state and I need to restart it?  The workaround for this is to create unique user events for each server, but then you've lost the advantage of the "single click" shutdown.
  2. To use a single user event requires all your server objects have the same data type for their StopEvent.  If the server objects are developed independently this can be very hard to ensure.  Furthermore, enforcing this policy puts unnecessary constraints on developing new server objects.  What if while writing a new server object I wanted to allow the client an option to include a comment for the log when shutting down the server?  I can't do without resorting to a clunky workaround, such as exposing a SetExitMessage method.
  3. By exposing the StopServer functionality as a user event instead of a method, you've forever locked in that data type.  Even if I wanted to change all my servers to include an optional comment I cannot go back and change that event's data type without breaking all the client code that has been built.  Had I used a sub vi, it is trivial to add an optional ExitMessage input.
  4. Looking at the server object's API, issuing some commands via sub vis and other commands via user events makes things more complicated for the client.  If I'm developing a client that uses your server and I see a LaunchServer method on the palette, it is reasonable to expect there would also be a StopServer method there.  There's no symmetry.  Exposing a RegisterStopEvent is a little better, but still not a great solution IMO.
  5. I mentioned it earlier but it bears repeating, issuing server commands via user events requires the client code to create and manage those user events.

It is possible to devise workaround to most or all of these issues, but why bother?  Exposing the StopServer functionality via a sub vi rather than a user event solves all of those problems and is so much easier in server code and in client code.  If the main advantage of using the user event is that the client code can close everything in a single action, write a StopAllServers sub vi that calls each server's StopServer method.  I'm open to the idea of injecting user events into code.  I think there might be valid reasons to do it (though I haven't thought of any yet), but I don't think this is one of them.  Is there some advantage that I've missed?

 

 

 


DFGray wrote:

Accessing data inside a server does indeed lead to race conditions.


 

The race conditions can still exist with ByRef data as opposed to the global data and will need to be managed.  That isn't what I was thinking about.  Let's say I've got a list of 100,000 words I need to run through the localization server.  To speed things up I want process the list in parallel, so I spawn four instances of the server and send each one a list of 25,000 words.  I can't do that if the data is maintained in a global and I don't know of a reasonable way for a .lvlib to maintain non-global data.  Switch it to a class and drop the data on an internal SEQ.  Now the client has more flexibility in how they can use your server.

 

 

 


Ben wrote:

First thought (tongue in cheek) Cheeter! :smileywink:

 

AS silly as it sounds, I refuse to "look at the back of the book for the solution" unitl after I have figured it out myself. After I have something that satifies my needs I will let myself look at other methods. 

 

Not silly at all.  I often do the same thing.  Smiley Happy  This time I needed to get working code out so I don't have the time to conduct my own R&D.

 

 

 


Ben wrote:

They would alos be a nightmare to try to port across Applications instance on a machine or between multiple machines.


 

I don't fully understand what your goals are, but on the surface it sounds like you're trying to make your active object do too much.  Write and test your active object for the local machine.  If you need client apps to access it across the network then you write proxy classes to handle the networking layer.

 

 


Ben wrote:

It hit me last night that if I convert my command and response queues over to classes then I can pass children of those Classes (children of the Command Class) and use over-ride VI of the Child class to test and cast as the appropriate types. Sorry if that doesn't make much sense yet.


 

It does make sense--that's the same thing I did, minus the response queue.  I actually have a UiActiveObject and an EngineActiveObject, so of course I have an EngineMessage class and a UiMessage class.  Both active objects also have the ability to output user events so I have an EngineEvent class and a UiEvent class.  The actual event types are subclasses of each of those two classes.  To be honest, I still don't know how well this is going to work out.  Time will tell I guess...

 

 

[Edited to try to fix the goofy formatting...]

Message Edited by Daklu on 01-14-2010 11:47 AM
Message 25 of 37
(2,184 Views)

Daklu wrote:
...

Ben wrote:

They would alos be a nightmare to try to port across Applications instance on a machine or between multiple machines.


 

I don't fully understand what your goals are, but on the surface it sounds like you're trying to make your active object do too much.  Write and test your active object for the local machine.  If you need client apps to access it across the network then you write proxy classes to handle the networking layer.

 

 


Ben wrote:

It hit me last night that if I convert my command and response queues over to classes then I can pass children of those Classes (children of the Command Class) and use over-ride VI of the Child class to test and cast as the appropriate types. Sorry if that doesn't make much sense yet.


 

It does make sense--that's the same thing I did, minus the response queue.  I actually have a UiActiveObject and an EngineActiveObject, so of course I have an EngineMessage class and a UiMessage class.  Both active objects also have the ability to output user events so I have an EngineEvent class and a UiEvent class.  The actual event types are subclasses of each of those two classes.  To be honest, I still don't know how well this is going to work out.  Time will tell I guess...

 

 

[Edited to try to fix the goofy formatting...]

Message Edited by Daklu on 01-14-2010 11:47 AM

You don't realize how much I value comments like that. Thank you!

 

For the time being I will leave that option open.

 

AS far as what I am trying to do....

 

 

THe first version of the software used to run this model railroad was written in BasicA and ran on an IBM-AT

 

I re-wrote it to learn LV in about LV 6.0. Both of those version only afted as a fancy interface to the hardware but had little logic and nothing was realy automatic.

 

So I am uisng this hobby to teach myself LVOOP since full automation is going to demand I learn how to handle a very complex Domain. When I am done I hope to have an automated train layout but also a very mean LVOOP library that can be used on simpler projects like... simulating a jet aircraft engine, or controlling a production line.

 

My initial designs of the Domain were screaming for Active Objects (Example: Crossing Gate or signal Towers)

 

 

Both can be implemented as Active Objects since they have to keep doing their thing while everything else is going on. In both those cases, they will watch some input ports (IR photo-detectors detect trains approaching crossoing gate from either direction) and when they see a change they take some action (close the crossing gate) and after some time do something else (open the crossing gate).

 

So I develop one Active Object that can take on the flavor of the various widgets and build on that.

 

Thanks for the feed-back!

 

Ben

 

PS that railroad is an N-Scale layout. The engines will fit in the palm of your hand and the steam variety can run as much as $200 an inch. So what you are looking at is my buddies rolling stock.

 

And if you like those images, wait until I get photos of the functional skip hoist we built to feed the balst furnace. We had to machine our own pulleys and capstans allof which would stack neatly on a dime.

Message Edited by Ben on 01-14-2010 02:44 PM
Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
Message 26 of 37
(2,178 Views)

@Daklu wrote:
I still don't like user events as a solution because...

For the reasons you mention, the original template includes a shutdown method.  The user event can be removed if you do not want to use it.  User events in this context are useful as a lossless broadcast mechanism (notifiers are not lossless).

 


@Daklu wrote:
...To speed things up I want process the list in parallel, so I spawn four instances of the server and send each one a list of 25,000 words...

I usually only use a server as a data source, and then only if the data collection is complex, such as a string server with multiple source files.  In the example you quote, the server itself should be modified to support parallel processing.  Launching four instances of the server breaks the interface paradigm and requires the calling code to know which server to call.  At that point, you may as well be using a simple global data structure.
Message 27 of 37
(2,134 Views)

Ben, $200 an inch is more than a little bit more than what I'm willing to spend to watch a train go around in circles.  😉  I admit that designing and rigging up all the ancillary stuff would be fun.

 

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

 


DFGray wrote:
For the reasons you mention, the original template includes a shutdown method.  The user event can be removed if you do not want to use it.  User events in this context are useful as a lossless broadcast mechanism (notifiers are not lossless).

 

I don't want you to think I'm arguing just for the sake of arguing, but I still don't see the advantage of user events in this situation.  I'm looking at this from the server developer's side of the street.  Why would I write a server that requires (or even allows) a client to inject a user event to trigger a shutdown?  There's no benefit for the server developers.  It's more stuff they have to manage and maintain.  I can't imagine there's a significant performance advantage.  Is there some use case for clients I've missed that outweighs all the disadvantages of the user event as compared to a method call?

 

You're using the user event instead of the notifier because the notifier is lossy.  That's fine, and as far as that goes I think it's correct.  The point I'm trying to make is that user events and notifiers are best used to broadcast information out of the server, not in to the server.  There's no reason to use either of them to shut down the server and lots of reasons not to use them.  Method calls aren't lossy either and a lot easier all around.

 

From the user perspective, "broadcasting" the shutdown to all servers via a user event appears on the surface to be a shortcut.  ("All I have to do is fire this user event to shutdown all my servers!")  Once you start digging into it I think most people will find the shutdown event more time consuming to code than a CloseAll sub vi that calls the Shutdown methods from each server in use.  ("All I have to do is call this sub vi to shutdown all my servers!")

 


DFGray wrote:
I usually only use a server as a data source, and then only if the data collection is complex, such as a string server with multiple source files.

 

If the data sources your sever depends on can't support multiple connections I agree there's no point in allowing mulitple instances to be created at runtime, and it would probably be wise to code it up to ensure it can't happen.

 

 


DFGray wrote: 
In the example you quote, the server itself should be modified to support parallel processing. 


 

 

That's the point.  The client can't choose to invoke multiple instances for parallel processing.  The server source code has to be modified to implement it, at which point a different client can't choose to have all the processing done in a single thread so other processes on the computer aren't starved for cpu time.  In writing the server you've limited the client's ability to use it in the way that makes the most sense for their situation.  You have to include parallel processing options in the server api and code all that logic into it, or you could simply write the server in a way that allows clients to spawn multiple instances if they want to.

 

I imagine the client code would look something like this.

 

Example_VI_BD.png

Message Edited by Daklu on 01-15-2010 03:05 PM
Message 28 of 37
(2,117 Views)

De gustibus non est disputandum!

 

I think we will have to agree to disagree on this one.  I see your points, and they are valid.  However, I like to use events to shut down multiple servers (I usually use response queues to ensure they have shut down).

 

In addition, the use case you quote for parallel processing does not appear to be a server, but one-shot processing which returns a set of strings.  For a server, the strings would remain in the server until fetched by accessor functions.  To me, writing a server allows you to avoid the type of client side code you have presented, making the users job much easier.  As you point out, it does require more of the server.  But I have found that this make maintenance and future expansion much easier.  However, this is my opinion.

 

Thanks for posting!

0 Kudos
Message 29 of 37
(2,077 Views)
Ben, are you using DCC yet?  It does simplify things quite a bit.  Implementing DCC in LabVIEW is a challenge, though.  The timing is tight enough that you will need a modern DAQ board or, much easier, an FPGA card to pull it off.  I did a demo system in LabVIEW 7.1 using cRIO a long time ago, but it worked very nicely.  Unfortunately, it is much cheaper to just buy the controlling system that to make it in LabVIEW 😞 .
0 Kudos
Message 30 of 37
(2,072 Views)