Why seems my object to become NULL from one line to the next? - Could it be a hardware thing?

173 views Asked by At

First thing to say: I program in a relatively unknown language: Blitzmax, which is a object oriented Basic dialect.

My problem is the following: I wrote a debugmanager which runs in an own thread. So from every position in the program (it will be a game) you can add debug- or errormessages to the manager's queue. In its own thread, it will fetch the messages from the queue and process them by writing them into a file and (if the message has the current chosen Debuglevel, Debugcategory and outputcategory, which are just enums) write it to the console.

Now I tested the program on three systems: My desktop PC which has Windows 8 as OS, my own laptop which has Windows 7 and the laptop of a friend which also has windows 7. On my PC and my friend's laptop everything is fine. But on my own laptop I get, nearly everytime, an "EXCEPTION_ACCESS_VIOLATION" Error while the manager is processing the messages. Sometimes the program just runs fine, but most of the time it breaks down with this error. Even in debugmode no line or stacktrace is shown which made it very hard to debug.

I broke all needed classes down to a minimum of attributes and functionality to make it easier to find the problem. Now the queue is just a list (which is natively build in in Blitzmax) and the message just has one attribute which is a string. Also the debugmanager only writes the message into the console without passing it to the process method which would write it to a file etc.

So the code which is actually needed is the following.

This is the message:

Type TThreadsafeMessage
     Field complete_String:String


     Method New_ThreadsafeMessage:TThreadsafeMessage(actual_Message:String, from_File:String, debugCategory:TDebugCategory_Enum,  ..
       debugLevel:TDebugLevel_Enum, outputCategory:TOutputCategory_Enum, from_Class:String = "", from_Method:String = "")

       'Just create the string from the parameters.
       Self.complete_String = actual_Message + " | " + from_File + "/" + from_Class + "/" + from_Method

        Return Self
     End Method

     Method ToString:String()
        'Just return the string attribute:
        Return Self.complete_String' out_String
     End Method

     Method toString_Formatted_For_File:String()
        Return Self.ToString()
     End Method

     Method toString_Formatted_For_Console:String()
        Return Self.ToString()
     End Method
End Type

This is the queue:

Type TThreadsafeQueue
     'Predefined list.
     Field list:TList

     Method New()
        Self.list = New TList
     End Method

     Method isEmpty:Byte()
        Return Self.list.IsEmpty()
     End Method

     Method enqueue(to_Enqueue:Object)
            'Add object to list
        Self.list.AddLast(to_Enqueue)
     End Method

     Method dequeue:Object()
        Return Self.list.RemoveFirst()
     End Method
End Type

Here is the method which adds messages to the debugmanager:

Function enqueueMessage(message_To_Enqueue:TThreadsafeMessage)
    'Test message for null pointer.
    If(message_To_Enqueue = Null) Then
        Throw New TNullpointer_Exception.NewException("'message_To_Enqueue' is NULL.", "TDebugmanager.bmx",  ..
            "TDebugmanager", "enqueueMessage")
    EndIf

    'Lock mutex for threadsafety.
    LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

    'Enqeue message in the queue
    TDebugmanager.getSingleton_Instance().message_Queue.enqueue(message_To_Enqueue)

    'Tell the update thread there is a message
    SignalCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable)

    'Free the mutex for update thread.
    UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)
End Function

Now here is the (currently smaller) update function of the debugmanager:

Function _update:Object(thread_Object:Object)
    'Do this over and over till the queue is empty AND the debugmanager is shut down
    Repeat
        Local message_To_Process:TThreadsafeMessage = Null

        'Lock mutex for thread safety
        LockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

        'Queue epmty...
        If(TDebugmanager.getSingleton_Instance().message_Queue.isEmpty()) Then
            '... Wait for a signal from the main thread
            WaitCondVar(TDebugmanager.getSingleton_Instance().sleep_ConditionVariable,  ..
                TDebugmanager.getSingleton_Instance().queue_Mutex)
        Else
            '...Get the next message from the queue.
            message_To_Process = TThreadsafeMessage(TDebugmanager.getSingleton_Instance().message_Queue.dequeue())
        EndIf

        'Unlock the mutex.
        UnlockMutex(TDebugmanager.getSingleton_Instance().queue_Mutex)

        'Check if the message is NULL.              
        If(message_To_Process = Null) Then
            Throw "Got null message from queue."
        EndIf

        'Actually the _processMessage method is used. But for debugging
        'it is commented out.   
    '   TDebugmanager.getSingleton_Instance()._processMessage(message_To_Process)

        'Write the message to the console.
        'HERE is the error.
        'See in the following description under the code section.
        DebugLog("Message processed: " + message_To_Process.complete_String)
    Until TDebugmanager.isFinished()
End Function

So at the position where it says in the update function 'HERE is the error.' the problem is the following: If this line is commented out or deleted, the program runs fine on my laptop and no error occures. But if this line is there, the error occures most of the times. An "EXCEPTION_ACCESS_VIOLATION" is thrown for example when: A stackoverflow occures somewhere. Or when you try to access to a NULL object. Actually everything which tries to read or write from forbidden memory. The really strange thing is: Only a few lines earlier, I check if the message which I got from the queue is NULL. It should throw an error, as you can see. But it never does.

Has anyone seen such a behaviour before? I cannot explain that. As I said: Debugging is really hard in this case. I could just break it down to smaller classes and finally the code you see here. I also cannot just go step for step through the program with the debugger because then no error occures. Can someone maybe think of something which can cause the error in this moment?

I know, this is much code, but I could not make it any shorter.

2

There are 2 answers

5
didierc On

You are basically writing concurrently to a stream. If DebugLog is not reentrant (eg. there's some static buffer used internally), you will get random access violation. On faster machine, that could be perhaps avoided because contention will happen at the message queue, hence each thread will have the time to do a full loop on its own.

The solution is to lock the use of that function (you may want to make a function wrapper which implement that locking independently of the queue processing).

0
M0rgenstern On

The problem was the following: When I tried to debug the program a little bit more, sometimes the debugger kicked in and showed me where the debugger thought the error occured. It didn't occure there, but I was able to use the step-In function of the debugger. The result was: It jumed into a library which is used for network methods. I do not use this library anywhere in the project. So I tried a little bit further and the actual solution was: Deinstall Blitzmax and reinstall it. Now everything works fine. Seems as if the linker was broken somehow.