Guest Posted September 21, 2020 (edited) Came across this today in my client-server project. An exception is thrown and catched for server-side logging. Then, the exception should be re-raised in order to go all the way down to the network communication layer. If sucessful the layer will foward the exception to the client. So ... this gives an AV: on E: Exception do begin LogErrors(E, SessionID, UserName, UserID); raise; // AV Here end; Edit: It also leaks one UnicodeString. But this works (!): on E: Exception do begin LogErrors(E, SessionID, UserName, UserID); raise Exception.Create(E.Message); end; I am not very versed in redaing the registers. Did i suceed to corrup the exception object? The LogError only does some type-checking on the exception and accesses it's Message member (works fine). Any pointers (debugging tips) would be appreciated. TIA, /Dany Edited September 21, 2020 by Guest Leaks are logical. Share this post Link to post
Der schöne Günther 316 Posted September 21, 2020 44 minutes ago, Dany Marmur said: The LogError only does some type-checking on the exception and accesses it's Message member (works fine) And everything it does is completely synchronous? No threads, task, TThread.Queue, TThread.Synchronize and whatnot? Share this post Link to post
Guest Posted September 21, 2020 The whole deep stack that produces the original Exception is ran in a dedicated thread managed by the network communication layer (RTC, multithreaded, events-based 3rd party library), i.e. everything happens as a reaction to a single request, in one thread (created and amnaged by RTC), also the raising and re-raising. The final handling of the exception is done by the network layer. When i wrote "works" above then the server and client handles the excptions correctly without any leaks. Share this post Link to post
Guest Posted September 21, 2020 I am not sure what is the problem for sure, but i can point two things here 1) About raising Exception, you are raising an exception while the original one is exception and it is E, the raised one is not the original one, on side note if you are handling the abort exception of RTC, then i suggest either, try to reraise an identical exception to E ( may be check the type/class and reraise it ), or steer away from that and make the LogErrors call on the upper level, like at final stage in the network communication layer, eg. the abort exception is rare in RTC but it is essential for its threading model so if E is the ERtcSystem or ERtcSocketError then your code is raising the default Exception. Since you want the exception to reach the transport layer, i assume it is not yours, hence you should be careful and make sure the exception with its parameters been passed as they raised. 2) Memory leak ?!, i think you should investigate that and fix it as it should not happen, may be there is a bigger problem that didn't show all symptoms, find it and fix it and that might solve your problem, as the first code of yours should be the right way to do it. Share this post Link to post
Guest Posted September 21, 2020 (edited) @Kas Ob., "the raised one is not the original one". True, and that is when everything works (as i expect it to). So apparently if i create another object (as i code snippet number 2) the memory management works, the original exception is freed properly. About RTC Exception, no, the original exception is raised from my code. It is not an RTC exception. But the AV is raised immediately (if i just re-raise) and not after my RTC event-response-code has finished. So it seems i have done something with the exception object before we reach the code above. Thank you, and @Der schöne Günther, i'll start a focused session and see what i can discern. Of course, i tried to remove the logging call... no!! Ooops! OK! Thanks *blushes*. Apparently the IDE did compile from chache or some such when i did that the first time. ARGH!! Happening in 10.4.1. Dangerous!! Fed up with this IDE. Have to build and restart constantly. Thanks guys! Edited September 21, 2020 by Guest IDE... YES i blame the IDE!! Share this post Link to post
David Heffernan 2345 Posted September 21, 2020 2 hours ago, Dany Marmur said: So ... this gives an AV: on E: Exception do begin LogErrors(E, SessionID, UserName, UserID); raise; // AV Here end; This is the correct way to do this. It works fine. The problem is elsewhere. Don't create and raise a new exception. Share this post Link to post
Guest Posted September 21, 2020 (edited) @David Heffernan, yes! This is why i started the thread. I asked for debugging tricks in order to pin down why that does not work. However, as i wrote above, the 10.4.1 IDE had me fooled. So now i have the "path" that i am used to to dig deeper inot this. If i find anything of value to other people than myself, or need more help, i'll post again. Thanks! Edited September 21, 2020 by Guest added my own failings Share this post Link to post
Mahdi Safsafi 225 Posted September 21, 2020 It's really hard to tell but from what you described I think you're facing a memory corruption, I'd say a stack corruption -based on the raise; fails, raise exception; works- I recommend the following things: - First, what happens if you omit the LogErrors call ? does the AV gone ? - Second, use madExcept and activate instanly crash on buffer this will help spotting heap corruption by over boundary. - Third, put BP on raise statement and fall into RaiseAgain an see what line/instruction is causing the AV. 1 Share this post Link to post
Guest Posted September 21, 2020 - First, what happens if you omit the LogErrors call ? does the AV gone ? Yes, amazingly. The E parameter is a const (no deal as it is a pointer) but it is only read, like "LogMsg := 'This'+ that + E.Message". I should publish that logging function here, but i'll wait until i've had the time to dig deeper. - Second, use madExcept and activate instanly crash on buffer this will help spotting heap corruption by over boundary. Good! Thanks! That is pertinent. Good idea, will-co! - Third, put BP on raise statement and fall into RaiseAgain an see what line/instruction is causing the AV. The AV is raised immediately on the re-raise "raise;" line. BOOM. I have traced into/out-of/and with strategic BPs. Thanks, @Mahdi Safsafi. Share this post Link to post
Guest Posted September 21, 2020 I just remembered my first question in this forum, it was if there is a cure for the side effect of the debugger caused leaks, then i had may be similar bug/leak, it was like going in circles, after hours and when i found the bug which was so obvious and i could spot it without the debugger, only the debugger was causing one leak per string evaluation, everywhere !, and i was running in circles. The more you trying to find a leak while debugging the more debugger will cause string related leaks and distract you, specially in the not so obvious cases, so make sure to check for the leaks without the debugger and be careful not to mix what is real from what is fake. Share this post Link to post
Remy Lebeau 1393 Posted September 21, 2020 4 hours ago, David Heffernan said: Don't create and raise a new exception. Sure, if you want code higher up on the call stack to catch and handle the original exception as-is. But there are use-cases where it makes sense to raise a new exception instead, in which case you should use the Exception.RaiseOuterException() method so that you can capture the original exception in the InnerException of the new exception so that it is not lost if the higher code needs it. Share this post Link to post
Guest Posted September 22, 2020 First my apologies for not being clear enough from the start. I postponed my deployment (another project) that got me in a rather stressed mode) a day and started digging. So this works in 10.3 but not in 10.4.1. "Obviously" i would not have produced that code if Berlin worked the same way. Happening over this was just by chance. I'm 100% sure there's a QP already. Granted, when i updated from Tokyo to Sydney i ran "cursory" tests since the project is only deployed as "Beta" to a few select persons. Just to avoid a series of comments about my negligence; the area of the application was not profiled yet, it is in the to-do. program ExceptionsReRaise; {$APPTYPE CONSOLE} uses System.SysUtils; function LogException(const E: Exception): string; begin Result := E.Message; //Logging('Blah blah' + Result, 'Exceptions'); FYI end; procedure LogExceptionEx(const E: Exception; var lResult: string); begin lResult := E.Message; //Logging('Blah blah blah' + lResult, 'Exceptions'); FYI end; var lMessage: string; begin try try raise Exception.Create('This is a raised Exception'); except on E: Exception do begin // LogException(E); // AV in Sydney, OK i Berlin LogExceptionEx(E, lMessage); // OK raise; end; end; { TODO -oUser -cConsole Main : Insert code here } except on E: Exception do begin Writeln(E.ClassName, ': ', E.Message); ReadLn; end; end; end. @Kas Ob., yes the leak was a debugger side-effect, my bad. Share this post Link to post
Lars Fosdal 1792 Posted September 22, 2020 Interestingly, the two following changes eliminates the AV on raise. Either, change LogException to a procedure procedure LogException(const E: Exception); begin Writeln('Log + ' + E.Message); //Logging('Blah blah' + Result, 'Exceptions'); FYI end; or assign the result to a variable: on E: Exception do begin lMessage := LogException(E); // AV in Sydney, OK i Berlin // LogExceptionEx(E, lMessage); // OK raise; end; It does look like a compiler problem. Is there a QP report? 1 Share this post Link to post
Guest Posted September 22, 2020 22 minutes ago, Lars Fosdal said: Is there a QP report? I have not filed any. Would the above code snipped be enought for a QP? In that case i can file it. I am more of a voter, the QC is a bit messy for me and frankly i fail to understand Embas answers... So much else to do and learn!! Share this post Link to post
Lars Fosdal 1792 Posted September 22, 2020 If you expand it with the examples that actually works - it would be sufficient for a QP, IMO. Share this post Link to post
Guest Posted September 22, 2020 Cool, Lars. Will make a try when i get the time (t'day or t'morrow). Share this post Link to post
Lars Fosdal 1792 Posted September 22, 2020 program SydnetExceptionsReRaise; {$APPTYPE CONSOLE} uses System.SysUtils; function LogExceptionFunc(const E: Exception): string; begin Result := E.Message; Writeln('Func Log + ' + E.Message); end; procedure LogExceptionProc(const E: Exception); begin Writeln('Proc Log + ' + E.Message); end; procedure Test1_OK; begin try try raise Exception.Create('This is a raised Exception'); except on E: Exception do begin LogExceptionProc(E); raise; end; end; except on E: Exception do begin Writeln('Test 1 ', E.ClassName, ': ', E.Message); end; end; end; procedure Test2_OK; var lMessage: string; begin try try raise Exception.Create('This is a raised Exception'); except on E: Exception do begin lMessage := LogExceptionFunc(E); raise; end; end; except on E: Exception do begin Writeln('Test 2 ', E.ClassName, ': ', E.Message); end; end; end; procedure Test3_Fail; begin try try raise Exception.Create('This is a raised Exception'); except on E: Exception do begin LogExceptionFunc(E); raise; end; end; except on E: Exception do begin Writeln('Test 3 ', E.ClassName, ': ', E.Message); end; end; end; begin try try Test1_OK; Test2_OK; Test3_Fail; except on E: Exception do begin Writeln('Unexpected ',E.ClassName, ': ', E.Message); end; end; finally Write('Press Any Key'); ReadLn; end; end. Here you go. No joke. Share this post Link to post
Guest Posted September 22, 2020 Yes, well... that makes it clearer, thanks! Share this post Link to post
Guest Posted September 22, 2020 (edited) @Lars Fosdal if you have all the logins and know what version of their QC is the correct one and you want to, then you are very welcome to ticket this. I'll vote for it 🙂. Silence and i will do it (procrastinated). Edited September 22, 2020 by Guest procrastination Share this post Link to post
Lars Fosdal 1792 Posted September 22, 2020 https://quality.embarcadero.com/browse/RSP-31084 Share this post Link to post