5

Some legacy code I'm stuck maintaining is stuck in an infinite loop (and thus I myself seem to be in one); I can't figure out why/how, though.

Here's the app's entry point, where it instantiates the main form (frmCentral):

CODE EXHIBIT A

public static int Main(string [] args)
{
    try
    {
        AppDomain currentDomain = AppDomain.CurrentDomain;

        currentDomain.UnhandledException += new UnhandledExceptionEventHandler(GlobalExceptionHandler);

        string name = Assembly.GetExecutingAssembly().GetName().Name;
        MessageBox.Show(string.Format("Executing assembly is {0}", name)); // TODO: Remove after testing <= this one is seen
        IntPtr mutexHandle = CreateMutex(IntPtr.Zero, true, name);
        long error = GetLastError();
        MessageBox.Show(string.Format("Last error int was {0}", error.ToString())); // TODO: Remove after testing <= this one is also seen

        if (error == ERROR_ALREADY_EXISTS)
        {
            ReleaseMutex(mutexHandle);

            IntPtr hWnd = FindWindow("#NETCF_AGL_BASE_", null);
            if ((int) hWnd > 0)
            {
                SetForegroundWindow(hWnd);  
            }
            return 0;
        }

        MessageBox.Show("made it into Main method #4"); // TODO: Remove after testing <= this one is seen

        ReleaseMutex(mutexHandle);

        MessageBox.Show("made it into Main method #5"); // TODO: Remove after testing <= this one is seen

        DeviceInfo devIn = DeviceInfo.GetInstance();

        MessageBox.Show("made it into Main method #6"); // TODO: Remove after testing <= this one is seen

        Wifi.DisableWifi();

        MessageBox.Show("made it into Main method #7"); // TODO: Remove after testing <= this one is seen

        // Instantiate a new instance of Form1.
        frmCentral f1 = new frmCentral();
        f1.Height = devIn.GetScreenHeight();
        f1.Text = SSCS.GetFormTitle("SSCS HHS", "", "");

        MessageBox.Show("made it before Application.Run() in Main method"); // TODO: Remove after testing <= this one is NOT seen
        Application.Run(f1);

        devIn.Close();

        Application.Exit();
        return 0;
    }
    catch(Exception ex)
    {
        SSCS.ExceptionHandler(ex, "Main");
        return 0;
    }
} // Main() method

frmCentral's constructor then calls a (what's the opposite of glorified?) singleton method named DBConnection.GetInstance():

CODE EXHIBIT B

public frmCentral()
{
    try
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        MessageBox.Show("made it past InitializeComponent() in frmCentral constructor"); // <= this displays
        devIn = DeviceInfo.GetInstance();
        MessageBox.Show("made it past DeviceInfo.GetInstance() in frmCentral constructor"); // <= this displays
        dbconn = DBConnection.GetInstance();
        MessageBox.Show("made it past DBConnection.GetInstance() in frmCentral constructor");
        WindowState = FormWindowState.Maximized;

        UpdateMenuItemSelectable = false;
        ResetConnectionFetchForm = false;

        AllowNewItems = true;

        listboxWork.Focus();
        MessageBox.Show("made it through frmCentral constructor"); // <= this one does NOT display
    }
    catch (Exception ex)
    {
        SSCS.ExceptionHandler(ex, "frmCentral()");
    }
} // frmCentral Constructor

Here's that "glorified" sort-of/kind-of singleton method:

CODE EXHIBIT C

// Singleton pattern, or at least a derivation thereof
public static DBConnection GetInstance()
{
    MessageBox.Show("made it into DBConnection.GetInstance()");
    try
    {
        if (instance == null)
        {
            MessageBox.Show("made it into DBConnection.GetInstance(); instance was null");
            instance = new DBConnection();
        }
    }
    catch(Exception ex)
    {
        SSCS.ExceptionHandler(ex, "DBConnection.GetInstance");
    }
    return instance;
}

That instantiates DBConnection and so its constructor is called:

CODE EXHIBIT D

private DBConnection()
{
    try
    {
        // Connection String
        string conStr = "Data Source = " + filename;
        string cmpStr = conStr + ".tmp";
        MessageBox.Show(string.Format("made it into DBConnection constructor. cmpStr == {0}", cmpStr)); // TODO: Comment out or remove
        if (File.Exists(filename+".tmp"))
            File.Delete(filename+".tmp");

        engine = new SqlCeEngine(conStr);
        MessageBox.Show(string.Format("SqlCeEngine created. conStr == {0}", conStr)); // TODO: Comment out or remove

        if (File.Exists(filename))
        {
            MessageBox.Show(string.Format("file {0} exists", filename)); // TODO: Comment out or remove
        }
        else
        {
            // Create the SQL Server CE database
            engine.CreateDatabase();
            MessageBox.Show("Made it past call to engine.CreateDatabase()"); // TODO: Comment out or remove
        }
        engine.Dispose();

        objCon = new SqlCeConnection(conStr);
        MessageBox.Show("Made it past call to new SqlCeConnection(conStr)"); // TODO: Comment out or remove

        objCon.Open();
    }
    catch(Exception ex)
    {
        SSCS.ExceptionHandler(ex, "DBConnection.DBConnection");
    }
}

I see the *MessageBox.Show()*s from Code Exhibit A (unless otherwise noted), then the same for Code Exhibit B, then Code Exhibit C, then Code Exhibit D, then it goes back and forth between C and D "until the cows come home."

I don't see why the DBConnection constructor and the DBConnection GetInstance() recursively call each other, though...is there a needle in the haystack I'm missing, or an elephant hidden in plain sight, or...???

UPDATE

public static void ExceptionHandler(Exception ex, string location)
{
    try
    {
        MessageBox.Show("Exception: " + ex.Message + "\n\nLocation: " + location, GetFormTitle("SSCS: " + ex.GetType().FullName,"",""));
    }
    catch(Exception exc)
    {
        MessageBox.Show("Exception Handler generated an exception!\n" + exc.Message + "\n\nCalling Location: " + location, GetFormTitle("SSCS: " + exc.GetType().FullName,"",""));
    }
}

UPDATE 2

Here's more edifying obscurities:

public static string GetFormTitle(string formName, string serialNo, string siteNo)
{

    string titleBar = formName == "" ? "SSCS HHS" : formName;

    if((serialNo == ""))
    {
        User person = new User();
        person.getUserFromTable();
        serialNo = person.getSerialNo();
    }

    if (frmCentral.HashSiteMapping.ContainsKey(siteNo))
    {
        siteNo = (string) frmCentral.HashSiteMapping[siteNo];
    }


    if (serialNo != "")
        titleBar += " - " + serialNo + (siteNo == "" ? "" : " Site #" + siteNo);

    return titleBar;
}

UPDATE 3

The uncaught exception code I added:

currentDomain.UnhandledException += new UnhandledExceptionEventHandler(GlobalExceptionHandler);

static void GlobalExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
    Exception e = (Exception)args.ExceptionObject;
    MessageBox.Show(string.Format("GlobalExceptionHandler caught {0}; Compact Framework Version == {1}", e.Message, Environment.Version.ToString()));
}

I have not seen any evidence that this handler is ever reached, though (so far, anyway).

UPDATE 4

Very interesting - after adding a MessageBox.Show (or two) to GetFormTitle:

public static string GetFormTitle(string formName, string serialNo, string siteNo)
{
    MessageBox.Show(string.Format("GetFormTitle() reached. formName == {0}; serialNo == {1}; siteNo == {2}", formName, serialNo, siteNo)); // TODO: Remove after testing
    string titleBar = formName == "" ? "SSCS HHS" : formName;

    if((serialNo == ""))
    {
        User person = new User();
        person.getUserFromTable();
        serialNo = person.getSerialNo();
    }

    if (frmCentral.HashSiteMapping.ContainsKey(siteNo))
    {
        siteNo = (string) frmCentral.HashSiteMapping[siteNo];
    }


    if (serialNo != "")
        titleBar += " - " + serialNo + (siteNo == "" ? "" : " Site #" + siteNo);
    MessageBox.Show(string.Format("titleBar val about to be returned. Val is {0}", titleBar)); // TODO: Remove after testing
    return titleBar;
}

...these are the sequence of of MessageBox.Show()s that I see now:

0) Made it into DBConnection.GetInstance() 
1) Made it into DBConnection.GetInstance() instance was null
2) Made it to DBConnection constructor cmpStr == ....
3) Sqlceengine created. conStr == ...
4) File \My Documents\HHSDB.SDF exists
5) Made it past call to new SqlCeConnection(conStr)
6) GetFormTitle() reached. fromName == SSCS:
System.Data.SqlserverCe.SqlCeException; serial No ==; siteNo ==

...followed by a round of more tail-chasing, recursive messages until I warm-boot (I hate to contradict the Fab 4, but contrary to popular opinion, Happiness is decidedly NOT a warm boot!*)

...so right in the midst of a MessageBox message display, an exception is inserted! Wholly Pennsylvania Hip-Hop (Key Rap)!

* Let's have no attempts at humor revolving around warm booty, now!

That was a superlative show of remote desk debugging, LB2!

B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • 23
    See: http://stackoverflow.com/questions/23436775/how-is-this-causing-an-endless-loop – Matt Burland May 02 '14 at 20:55
  • 4
    I could spend the rest of the day on that. – B. Clay Shannon-B. Crow Raven May 02 '14 at 20:57
  • 2
    My first suggestion would be: "don't debug with Alert". Use `System.Diagnostics.WriteLine` and watch the debug window while running. `Alert` will cause execution to halt. Or if you need to halt execution, set a breakpoint and step through. – Matt Burland May 02 '14 at 20:57
  • Breakpoints not possible in this project/setup (if only there were). This is a CE/CF project that runs on ancient devices. – B. Clay Shannon-B. Crow Raven May 02 '14 at 20:59
  • I don't see it, but one of the reason for saying don't debug with `Alert` is because with the one after `if (instance == null)` any other attempt to access `GetInstance()` is going to pile up additional alerts after that. So I suspect it's not the case that case that `DBConnection` is calling `GetInstance`, but that something else is calling `GetInstance` multiple times and, because of the alert, they all end up calling `DBConnection`. Are you absolutely certain that this is the only place `GetInstance` gets called? – Matt Burland May 02 '14 at 21:05
  • @MattBurland: Hec Ramsey no! GetInstance() is called from a jillion places, but I don't see how they are getting the opportunity on startup to do so... – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:08
  • @MattBurland: I also don't understand why every time GetInstance is called, "instance" is null - why nil it willy-nilly like that? And why the main form has a DBConnection var that it assigns the value of DBConnection's DBConnection "instance" var to - why not just the public "instance" directly? The whole architecture/thought process behind this BBOM drives me batty. – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:12
  • 1
    If you can't use breakpoints, and GetInstance() is called from a jillion places, I suggest using StackTrace to write out who is calling GetInstance. That will probably tell you very quickly why it's getting called multiple times. – hatchet - done with SOverflow May 02 '14 at 21:13
  • 2
    What does `SSCS.ExceptionHandler` do internally? does it rethrow? Is it possible that `objCon.Open();` throws, which possibly makes exception bubble up, implies that `instance` never gets set. And thus _"gazillion places where `GetInstance` get's called"_ makes it appear that it is an infinite loop? – LB2 May 02 '14 at 21:13
  • 1
    My best suggestion would be to refactor `GetInstance` into a proper singleton pattern with thread-safety and see if that makes the problem go away. Either that or hunt down the original developer and punch them in the nose. – Matt Burland May 02 '14 at 21:15
  • 1
    Actually, another suggestion would be a double check on null. Silly as it sounds, add another `if(instance==null)` *after* your alert. If that stops the alerts from `DBConnection` then you know the problem is that you have a race condition on calling `GetInstance`. If you still see (more than one set of) alerts from `DBConnection`, then `instance` must be getting set to null somewhere. – Matt Burland May 02 '14 at 21:19
  • 3
    @MattBurland: Tempting, but he has absquatulated to nether regions. To be fair, though, as one coder I formerly worked with noted once, "Everybody is an idiot after they leave, no matter how revered they were while they were there." – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:19
  • @MattBurland: "instance must be getting set to null somewhere" Yes, it is; don't understand why, and don't want to change things more than absolutely necessary, as this is definitely a "whack-a-mole" situation and "you break it (more), you bought it" – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:20
  • Chaser to my earlier comment: Can you please post `SSCS.ExceptionHandler` body as I suspect the secret is there? Oh, and also, put debug messages in relevant catches so you can see if exception is being thrown or not. – LB2 May 02 '14 at 21:20
  • @LB2: I put it in UPDATE – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:22
  • 1
    @B.ClayShannon Oh my, a try/catch around messagebox only for the catch to show a messagebox with just about the same construct!!! Precious! This needs to go into some sort of archive of book of records... :) – LB2 May 02 '14 at 21:25
  • @B.ClayShannon Question: Can `GetFormTitle` throw or cause an exception? If yes, it's possible you're not seeing the msgboxes in your catch, and yet keep having them. Can you try replace catch's message box with a very plain one that doesn't call anything else? (I am grasping at straws here too) – LB2 May 02 '14 at 21:28
  • @LB2: If you saw the rest of your code, your hair would fall out and your toes would involuntarily curl back. – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:30
  • @LB2: Going back a few comments, if objCon.Open(); "threw" I would see the catch block's message, right? – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:31
  • @MattBurland: "because of the alert, they all end up calling DBConnection" Why would the MessageBox cause DBConnection to be called? The app had been endless-looping prior to my adding the MessageBoxes; they show me just what code is being reached, though. – B. Clay Shannon-B. Crow Raven May 02 '14 at 21:33
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/51913/discussion-between-lb2-and-b-clay-shannon) – LB2 May 02 '14 at 21:34
  • Because you have the alert *after* the null check but *before* you assign a value to . – Matt Burland May 03 '14 at 03:15
  • New related question here: http://stackoverflow.com/questions/23505211/which-sqlserverce-dll-version-do-i-need-to-reference-in-my-project-to-match-what – B. Clay Shannon-B. Crow Raven May 06 '14 at 21:57

1 Answers1

6

Here is what's likely going on (at this point is a theory until OP can confirm):

The thing that starts the problem...

The issue that starts the infinite loop chain is likely objCon.Open(); in Exhibit D. There is probably some issue with connecting to the database, and the Open() call, as it should, throws an exception. This of course get caught in the local catch that is immediately following that line.

The local catch calls SSCS.ExceptionHandler which is shown in Update 1. It looks benign, but in it hides the culprit's accomplice by the name GetFormTitle which is shown in Update 2.

Where things take a turn for the worse...

GetFormTitle has a very interesting piece of code:

User person = new User();
person.getUserFromTable();

... which most likely is a model retrieving user info from the database (where else).

And so the infinite loop is created...

Well, to get User from database, one needs a connection, which most likely causes DBConnection.GetInstance() to be called, which gets to objCon.Open(); which starts the new cycle, and thus an infinite loop is created (ingeniously, without using any of language's built-in looping mechanisms, to be duly noted).

To confirm:

OP: Please put very plain (meaning no calls to GetFormTitle, please) MessageBoxes in the GetFormTitle, and if the above theory is right, you'll see it in the path of execution.

Community
  • 1
  • 1
LB2
  • 4,802
  • 19
  • 35