Order of Processing Woes

Posted by Andy Futrell on 08-Jun-2012 14:11

I was just hoping someone may be able to point me in the right direction on some odd behavior that I saw with an object I created. I'm sure I've done something wrong, but when trying to debug/troubleshoot it becomes quite interesting.

It is difficult to explain, but it seems that if I have a ClassA and ClassB where B inherits A, that there are some (as yet unknown) circumstances where my SUPER stuff will fire before the child even though the code in the child fires the SUPER stuff afterwards?  And I'm also having issues with buffers not being exclusive locked, that per the code should be on subsequent calls to the class (separate objects in a new run). Even had a stream conflict with one of them on two separate runs.

I guess I'm needing help understanding the persistance of a CLASS across different runs of a program that instantiate objects from that class. And perhaps what happens on different runs naming things the same, etc.

So, I know that's vague, here's an example as best as I can explain it...

Class AParent

  Method Init:

     create dbrecord MyLog.

     set various MyLog.values.

  End Method.


  Method updateStatus(pStatus as char):

     if available(MyLog) then MyLog.Status = pStatus.

  End Method.


  Method Fail:

    updateStatus("Failed").

    logMessage = "I failed".

  end Method.


  Method Success:

    Do O/S Stuff.

    If O/S error then FAIL.

    else do:

      updateStatus("Success").

      logMessage = "I Succeeded"

    end.

  end method.

  Method logMessage(pmessage as char):

    MyLog.Message = pmessage.

    MyLog.MessageTime = now.

  end method.

end class.



Class BChild inherits AParent

  Method Fail:

    Do O/S Stuff

    SUPER:Fail

  end method.


  Method Success:

    Do O/S Stuff.

    if error then SUPER:Fail

    else SUPER:Success

  end method.

end class.

The above is fairly simplistic but is the overall idea of what I'm trying to do. So when I run this, most of the time everything is great. But on a few occasions, I had an error telling me that the database table "MyLog" had a no-lock status and could not be updated. Then I also had a stream error once (one of the real methods actually outputs some info to the o/s via put stream) and it was basically saying the stream was in use.

When testing, I have a program "testThis.p" that is basically something like:

def var myObj as class BChild.

myObj = new BChild.

myObj:Init.

loop and do some myObj stuff that does myObj:logMessage calls

at end do myObj:fail or myObj:success.

And I'm finding small wholes etc. and fixing the code, recompiling and then re-running the above program. So I was wondering if maybe "myObj" is persisting, I'm not cleaning something up right or what not.

Also, on the ones that work things seem ok, but the ones that went awry things fired out of order. So for example, I may expect to see a log file like this:

│Date/Time               Status   Message                                                               │

│─────────────────────── ──────── ──────────────────────────────────────────────────────────────────────│

│06/08/2012 12:02:48.110 Info     Initializing Run.                                                     │

│06/08/2012 12:02:48.499 Info     Initialization complete.                                              │

│06/08/2012 12:02:52.222 Info     line 1                                                                │

│06/08/2012 12:02:52.894 Info     line 2                                                                │

│06/08/2012 12:02:56.221 Info     line 3                                                                │

│06/08/2012 12:03:39.209 Info     line 4                                                                │

│06/08/2012 12:03:39.212 Warning  Unable to copy file /fsb/qad/eb21/transfers/blacksmith/tmp/100_product│

│06/08/2012 12:03:39.214 Warning    File OR directory does NOT exist.                                   │

│06/08/2012 12:03:39.215 Error    Unable to copy file to Outbound Directory.                            │

│06/08/2012 12:03:39.218 Warning  Unable to move file /fsb/qad/eb21/transfers/blacksmith/tmp/100_product│

│06/08/2012 12:03:39.219 Warning    File OR directory does NOT exist.                                   │

│06/08/2012 12:03:39.220 Error    Unable to move file to Error Directory.                               │

│06/08/2012 12:03:39.222 Error    Process completed with errors. Review Log. 

But Instead I saw this:

│Date/Time               Status   Message                                                               │

│─────────────────────── ──────── ──────────────────────────────────────────────────────────────────────│

│06/08/2012 12:02:48.110 Info     Initializing Run.                                                     │

│06/08/2012 12:02:48.499 Info     Initialization complete.                                              │

│06/08/2012 12:02:52.222 Info     line 1                                                                │

│06/08/2012 12:02:52.894 Info     line 2                                                                │

│06/08/2012 12:02:56.221 Info     line 3                                                                │

06/08/2012 12:02:56.226 Info     Process completed successfully.                                       │

│06/08/2012 12:03:39.209 Info     line 4                                                                │

│06/08/2012 12:03:39.212 Warning  Unable to copy file /fsb/qad/eb21/transfers/blacksmith/tmp/100_product│

│06/08/2012 12:03:39.214 Warning    File OR directory does NOT exist.                                   │

│06/08/2012 12:03:39.215 Error    Unable to copy file to Outbound Directory.                            │

│06/08/2012 12:03:39.218 Warning  Unable to move file /fsb/qad/eb21/transfers/blacksmith/tmp/100_product│

│06/08/2012 12:03:39.219 Warning    File OR directory does NOT exist.                                   │

│06/08/2012 12:03:39.220 Error    Unable to move file to Error Directory.                               │

│06/08/2012 12:03:39.222 Error    Process completed with errors. Review Log. 

The line in red above really shouldn't have even fired much less come out BEFORE line 4 info since lines 1-4 were logMessage calls that happen in the calling program prior to calling the "Success" method which writes that line. And the success method shouldn't have written that line because it failed and hence generated the warning/error lines below.

I'm sure it's something I need to be doing in the test program or destructor or something. But just not finding it. And I can't really reproduce it (yet.)  I know it's not enough to debug my actual problem, but may turn on some thoughts as to what could be happening, where to look, etc.

Any thoughts (including "go read this") very much appreciated.

All Replies

Posted by Thomas Mercer-Hursh on 08-Jun-2012 14:36

This isn't really answering your question, but ...

I would avoid the problem by not setting things up this way.  My recommendation is to *not* duplicate method names between A and B with one exception and that is where a method is defined as abstract in A and the implementation is in B, i.e., it is functioning in the same fashion as an interface.  Having a clear and distinct signature for each is going to eliminate the kind of issues you are facing and I think is just good OO design because it recognizes the distinctiveness of A and B.  I.e., if there is some common stuff to do when one fails, put that in A and then define a method in B that is BFail or whatever (hopefully conveying why this is different) and have it do what it needs to do and call the A:Fail when and if appropriate.  If it makes no sense for A:Fail to be called directly by anyone outside, then make it protected, call it something like CommonFail, and you can name the method in B just plain Fail.

BTW, I hope there is a C which also inherits from A since it is an OO no-no to have only one child for a parent.

Posted by Andy Futrell on 08-Jun-2012 15:10

tamhas wrote:

This isn't really answering your question, but ...

I would avoid the problem by not setting things up this way.  My recommendation is to *not* duplicate method names between A and B with one exception and that is where a method is defined as abstract in A and the implementation is in B, i.e., it is functioning in the same fashion as an interface.  Having a clear and distinct signature for each is going to eliminate the kind of issues you are facing and I think is just good OO design because it recognizes the distinctiveness of A and B.  I.e., if there is some common stuff to do when one fails, put that in A and then define a method in B that is BFail or whatever (hopefully conveying why this is different) and have it do what it needs to do and call the A:Fail when and if appropriate.  If it makes no sense for A:Fail to be called directly by anyone outside, then make it protected, call it something like CommonFail, and you can name the method in B just plain Fail.

I actually thought of doing that, just didn't get to that point yet. Although, I'm not complete convinced that I agree with it being "good OO".  For example, let's say you have ClassParent, it defines method Fail. In that, you do normal fail work.  Then you have sub-clasess A-whatever. You may want A to get the generic failure, B to do something special, C to do something a little different from B, etc. So conceptually, I think it makes more sense for them to be the same name IF there could be cases/sub-classes where you'd want to just use the super.

(That said, in my current use case, the super:Fail is actually protected and always overridden, so I'm ok with renaming it... Not sure that's the issue though.)

BTW, I hope there is a C which also inherits from A since it is an OO no-no to have only one child for a parent.

Yep :-) There is another child. The original design was just the main class with some case statements in a few of the methods, and a property that wasn't really needed for one of the cases. I decided they were different enough that they should really be two child classes. Much easier to understand from a conceptual point.

Posted by Thomas Mercer-Hursh on 08-Jun-2012 15:39

They really should be different names because they are different behavior responsibilities.  It is perfectly reasonable to have A with StandardFail and B, C, and D children with just Fail(), the method using classes call, and for B's Fail method to consist of nothing more than a call to StandardFail.  Even though B:Fail() does nothing that StandardFail() doesn't do, they have different roles.  One is the method which using classes call in case of failure and the other is the common failure action for this inheritance tree.

BTW, you might also want to consider delegation as an alternative to inheritance.

Posted by jmls on 08-Jun-2012 15:47

your pseudo code has me somewhat confused. Easy, I know, but still ..

are you creating a new dbrecord

1) for every change of status,

2) for every logmessage,

3) or simply for each instance of the class ?

#0  Each method has it's own buffer. There are no scoping problems, no bleeding locks.

#1 Each db update must be wrapped in a transaction

so your code should look like:

Class AParent:

def var SomeID as char no-undo.

Method Init:

def buffer MyLog for MyLog.


do transaction:

     create dbrecord MyLog.

     set various MyLog.values.

assign SomeID = MyLog.UniqueID.

end

  End Method.

Method updateStatus(pStatus as char):

def buffer MyLog for MyLog.


do transaction:

     find MyLog where MyLog.UniqueID eq SomeID exclusive no-wait no-error.

     if available(MyLog) then MyLog.Status = pStatus.

end.

  End Method.

etc etc. The difference in speed is neglibile. Try it.

Regarding the red line,

#1 look at the index that you are using to display the records

#2 make absolutelty sure that you are not mixing records from different logs ..

Posted by Andy Futrell on 08-Jun-2012 16:15

are you creating a new dbrecord

1) for every change of status,

2) for every logmessage,

3) or simply for each instance of the class ?

#0  Each method has it's own buffer. There are no scoping problems, no bleeding locks.

#1 Each db update must be wrapped in a transaction

Well... 2 & 3 actually. There's actually a parent/child database thing. I have one "Log" record in on table. And them multiple "LogEntry" records in another table. So my init, creates the "Log" table record and nothing else (one for each instance). Then my logMessage method creates a single "LogEntry" record with the message. And there's actually status record in both the Log and LogEntry records as the caller may do a "Success" call so I want to log that even though the whole thing may fail in the end when I'm wrapping things up. (Long story there, but moving files at o/s level, etc. after the user is "done".)

So I would have thought no scoping/bleeding issues either but alas, something was off... However, what if my program crashed or did an abend and my destructor doesn't do anything. Is it *possible* that Progress left something lying around? That's what seemed to have happened. This happened on a test case where I didn't have some things set up correctly so my program which should have worked, failed for a reason my code hadn't considered and it never completed everything on the run before this odd one.

Class AParent:

def var SomeID as char no-undo.

Method Init:

def buffer MyLog for MyLog.


do transaction:

     create dbrecord MyLog.

     set various MyLog.values.

assign SomeID = MyLog.UniqueID.

end

  End Method.

Method updateStatus(pStatus as char):

def buffer MyLog for MyLog.


do transaction:

     find MyLog where MyLog.UniqueID eq SomeID exclusive no-wait no-error.

     if available(MyLog) then MyLog.Status = pStatus.

end.

  End Method.

I do have SomeID defined so good there. However, I do not have my code doing a new find in each method/property. That was a question I had. Is that really:

A - Needed?

B - Best Practice?

That would seem to cause a lot of extra work.  My actual class has several methods and several properties that act on that database record. So are you suggesting that each method/property needs to re-find that record? Then each of my properties that encapsulate a field of that record would need to re-read the record? Each method that uses the record needs to read the record?

I started down that path, but that seemed overkill and also didn't match up with some of the examples I had seen. It seems to me it would make since to scope the record to the class. But, I'd certainly be interested in everyone's thought and experience on that because I was heading in that direction and it sounds like you suggest that as well. (Although, now that you have me thinking more about transaction scope that alone may make it necessary to do all of those extra reads, at least for the places I need to do a write.)

#1 look at the index that you are using to display the records

#2 make absolutelty sure that you are not mixing records from different logs ..

The index is good, it's a datetime field at the end so messages unique to the ms, and I'm not mixing logs unless something didn't get cleaned up and was hanging around from a prior run. See above note, but the run before the one that had out of order statements had failed in a case that I was not catching so I'm of the opinion that something was still around in the world...

Thank you much for the thoughts.

Posted by bheavican on 08-Jun-2012 16:21

Thank you for your message. I am currently out of the office, with limited access to email.

I will be returning on Monday, 06/18/12.

Brent

Posted by Andy Futrell on 08-Jun-2012 16:46

jmls wrote:

Class AParent:

def var SomeID as char no-undo.

Method Init:

def buffer MyLog for MyLog.

do transaction:

     create dbrecord MyLog.

     set various MyLog.values.

assign SomeID = MyLog.UniqueID.

end

  End Method.

etc etc. The difference in speed is neglibile. Try it.

Thanks Julian. I had to step away and think and you pretty much have to do the db work inside of each method/property otherwise things could get funny. I was making a (false) assumption that the object would be around and working properly as long as I needed to access things and that's just not neccesarily true. For instance, I had an o/s error and that made things behave different that my code expected so, quite possible that something was strange. I guess if I re-do finds in every case, I'm always at least sure of the scope and type of lock.

That said... what about READS of that record?  Would you just have every single method/property that needs to access the record read it (no-lock or exclusive, whichever it needs?)

Thanks again. It's a lot of find statements but I guess in the end, the thing should be in memory and I'm accessing with a unique key so it really shouldn't take a lot of time. (I hope).

Posted by jmls on 09-Jun-2012 03:03

afutrel wrote:

Thanks Julian. I had to step away and think and you pretty much have to do the db work inside of each method/property otherwise things could get funny. I was making a (false) assumption that the object would be around and working properly as long as I needed to access things and that's just not neccesarily true. For instance, I had an o/s error and that made things behave different that my code expected so, quite possible that something was strange. I guess if I re-do finds in every case, I'm always at least sure of the scope and type of lock.

That said... what about READS of that record?  Would you just have every single method/property that needs to access the record read it (no-lock or exclusive, whichever it needs?)

Thanks again. It's a lot of find statements but I guess in the end, the thing should be in memory and I'm accessing with a unique key so it really shouldn't take a lot of time. (I hope).

Again, I generally always scope the table to the method, so a find would have to be re-read. IMHO it's always best to try and make the scope of the buffer as small as possible. Less surprises that way

This thread is closed