Page 1 of 1

How to solve random invalid memory access in thread?

Posted: Mon Apr 29, 2013 5:30 am
by calixarene
Hi,

I got random invalid memory access when I try to use a thread in my program that communicate through serial port. When there is no thread the program seems to work ok but the program interface is not responsive when I stress it with a lot of communication. That is why I want to implement a thread. Here is part of the code for you guys to help.

Code: Select all

Structure SerialSetting
  SerCommPort.i
  BaudRate.i ; 
  Parity.i
  DataBit.i
  StopBit.i
  HandshakeMode.i
  InputBufferSize.i
  OutputBufferSize.i
  Port.s ;Com port as string
EndStructure

Structure BCRaw
  TimeStamp.i
  Flow.d
  Array BC.d(0)
  Array ATN.d(0)
EndStructure

Procedure HandleSerialData(*Value) ; A thread to handle the serial port data coming in
  Shared StopFlag.i,DataReceived.i
  Shared PortCfg
  StopFlag =#False ; Turn on the monitor loop
  Define ByteReceived.i 
  ;Max character input rate is 14400 CPS @115200 bauds 7 databit And 1 stopbit, no parity check And no handshaking
  ;If the set delay is 30 millisec, each cycle will have at most 480CPS, 512 byte will be sufficient
  Shared *Buffer
  Define TempStr.s 
  If IsSerialPort(PortCfg\SerCommPort) ; Ensure comm port is open properly   
    Repeat    
      ByteReceived =AvailableSerialPortInput(PortCfg\SerCommPort)
      ;      
      While ByteReceived 
        DataReceived =#True
        If ByteReceived > 512
          ByteReceived = 512
        EndIf
        If ReadSerialPortData(PortCfg\SerCommPort, *Buffer, ByteReceived) ; Read at most 512 characters
          ProcessData(*Buffer, ByteReceived) ; Send to the data parasic routine
        Else
          ;Error needs to be addressed during the port reading operation
          TempStr = HandleSerialPortError()
          If TempStr =""
            UpdateStatusLable("Unknown Error occurred")
          Else
            UpdateStatusLable(TempStr)
          EndIf
          StopFlag = #True
          Break
        EndIf
        ByteReceived =AvailableSerialPortInput(PortCfg\SerCommPort)
      Wend
      DataReceived=#False ; Turn off the DataReceived light so to give better visual representation to user - flashing
      Delay(30) ; Wait to see whether there is further input from the communication port
      ByteReceived =AvailableSerialPortInput(PortCfg\SerCommPort)
    Until StopFlag Or ByteReceived =0
    DataReceived=#False
  EndIf
  ; Thread running completed means no more data need to process for the time being
EndProcedure
Procedure ProcessData(*Buffer, BufferLength)
  Static CarryOverStr.s
  Define EndPosition.i, NumOfField.i
  Define TempStr.s, LocalStr.s,ReportStr.s
  Shared CaptureFile, CaptureState, UpdateFlag.i,DataIndex,UBData
  
  CarryOverStr= CarryOverStr+ PeekS(*Buffer,BufferLength)
  If ErrorFlag =#False ; This flag control to raise the error only once
    If CarryOverStr="" And BufferLength >0
      ErrorFlag = #True
      UpdateStatusLable("Wrong communication setting with device such as baud rate, stop bit...Please check.")
    EndIf
  EndIf
  EndPosition = FindString(CarryOverStr,#LF$ )
    While EndPosition <> 0;if endoposition <>0
    
    TempStr = Left(CarryOverStr, EndPosition-1) ; Remove the LineFeed
    TempStr = RTrim(TempStr,#CR$) ;Remove the carriage return at the end if it is present
    EndPosition = EndPosition +1
    If EndPosition <Len(CarryOverStr) ; The carryoverStr has characters after the linefeed
      CarryOverStr =Mid(CarryOverStr,EndPosition) ; Cut the process string and leave the remains
    Else
      CarryOverStr="" ; otherwise clear the string
    EndIf
    ; Write Data to disk
    If CaptureState =#True
      WriteToDiskN(CaptureFile, TempStr)
    EndIf
    ;updatedisplay
    AddGadgetItem(#ScrollText,-1, Tempstr ) ; Add the line at the end
    ;Parase the Data 
    NumOfField =CountString(TempStr,",")   
    Select  InstrumentType
      Case #AE1X
        If NumOfField =9
          With BCData(DataIndex)
            \TimeStamp = BCTimeStampToDate(StringField(TempStr,1,","),StringField(TempStr,2,","))
            ReportStr =FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss",  BCData(DataIndex)\TimeStamp)  
            LocalStr = StringField(TempStr,3,",")
            ReportStr = ReportStr +Chr(10) + LocalStr 
            \BC(0) =Val(LocalStr)
            UpdateBCPanel(LocalStr) ; Update the panel with BC value
            LocalStr = StringField(TempStr,4,",")
            ReportStr = ReportStr + Chr(10) + LocalStr 
            \Flow = Val(LocalStr)
            LocalStr = StringField(TempStr,10,",")
            ReportStr = ReportStr + Chr(10)  + LocalStr 
            \ATN(0) = Val(LocalStr)
            
          EndWith 
        EndIf
      Case #AE2X
        ;If ArraySize(BCData(DataIndex)\BC()) =1
          With BCData(DataIndex)
            
            \TimeStamp = BCTimeStampToDate(StringField(TempStr,1,","),StringField(TempStr,2,","))
            ReportStr =FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss",  BCData(DataIndex)\TimeStamp)   
            LocalStr = StringField(TempStr,3,",")
            ReportStr = ReportStr +Chr(10) + LocalStr 
            \BC(0) =Val(LocalStr)
            UpdateBCPanel(LocalStr) ; Update the panel with BC value and it is store in \BC(0)
            LocalStr = StringField(TempStr,4,",")
            ReportStr = ReportStr +Chr(10)+ LocalStr  
            \BC(1) = Val(LocalStr)
            LocalStr = StringField(TempStr,5,",")
            ReportStr = ReportStr  +Chr(10) + LocalStr
            \Flow = Val(LocalStr)
            LocalStr = StringField(TempStr,11,",")
            ReportStr = ReportStr +Chr(10) + LocalStr 
            \ATN(0) = Val(LocalStr)
            LocalStr = StringField(TempStr,17,",")
            ReportStr = ReportStr +Chr(10) + LocalStr 
            \ATN(1) = Val(LocalStr)
            
          EndWith
        ;Else
          If ErrorFlag=#False
          UpdateStatusLable("Data structure is not set up for AE2X. Please reset the configuration")
          EndIf
        ;EndIf
      Case #AE3X
        Define ICounter.i
        With BCData(DataIndex)
        ;If ArraySize(\BC()) <5
          
            \TimeStamp = BCTimeStampToDate(StringField(TempStr,1,","),StringField(TempStr,2,","))
            ReportStr =FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss",  BCData(DataIndex)\TimeStamp)  
            For ICounter= 0 To 6
              LocalStr = StringField(TempStr,3+ICounter,",")
              ReportStr = ReportStr + Chr(10) + LocalStr  
              \BC(ICounter) =Val(LocalStr)
            Next
            LocalStr = StringField(TempStr,10,",")
            ReportStr = ReportStr + Chr(10) + LocalStr  
            \Flow = Val(LocalStr)
            For ICounter= 0 To 6
              LocalStr = StringField(TempStr,16+ICounter*6,",")
              ReportStr = ReportStr +Chr(10)+ LocalStr   
              \ATN(ICounter) =Val(LocalStr)
            Next
            UpdateBCPanel(StrSciD(\BC(5),9,8)) ; The sixth record stores the bc value
          
        ;Else
          If ErrorFlag=#False
            UpdateStatusLable("Data structure is not set up for AE3X. Please reset the configuration")
            ErrorFlag =#True
          EndIf
        ;EndIf
        EndWith
    EndSelect    
    UpdateListData(ReportStr)
    
    UpdateFlag.i =#True
    ;update database
    DataIndex =(DataIndex +1)%(UBData+1) ;Rolling buffer
    EndPosition = FindString(CarryOverStr,#LF$)
  Wend
EndProcedure
Procedure UpdateStatusLable(UpdatedMsg.s)
  #UBIndex = 4
  Static Dim SystemMsg.s(#UBIndex-1)
  Static Index.i
  Define Counter.i
  SystemMsg(Index)= GetCurrentTimeStamp(Date(),#TimeOnly) + Space(3) + UpdatedMsg 
  UpdatedMsg=""
  For Counter =1 To #UBIndex 
    UpdatedMsg=UpdatedMsg + SystemMsg((index + Counter)%(#UBIndex)) + #CRLF$ 
  Next
  Index =((index+1 )%#UBIndex) ;
  SetGadgetText(#LblStatus, UpdatedMsg)
EndProcedure
Procedure ChangeInstrumentType(InstrumentType.i)
  Define ICounter.i
  Shared UBData
  If InstrumentType = #AE3X
    For ICounter = 0 To UBData
      ReDim BCData(ICounter)\BC(6) ; Total seven parameters
      ReDim BCData(ICounter)\ATN(6)
    Next
  ElseIf  InstrumentType = #AE2X   
    For ICounter = 0 To UBData
      ReDim BCData(ICounter)\BC(1) ; Total Two parameters
      ReDim BCData(ICounter)\ATN(1)
    Next
  Else 
    ;InstrumentType =#OptionSingle 
    For ICounter = 0 To UBData
      ReDim BCData(ICounter)\BC(0) ; only one parameters
      ReDim BCData(ICounter)\ATN(0)
    Next
  EndIf
  AethalometerType = InstrumentType
  ReconfigListData()
EndProcedure

Procedure WindowMain_Events(EventIs.i)
  Shared WindowTop, WindowLeft,DCDStatus, WindowCloseFlag.i, FilePattern,PatternSelected,iniFile,AppDirectory.s
  Define EventTimerIs.i
  Static CheckCount.i ; This must be static for checking the status 
  Select EventIs
    Case #WaitEventTimeout ; this is equal to 0 as timeout in waitevent return zero
      If IsThread(HandleSerialThreadID)=#NotRunning ; When there is no more serial data is being process 
        ;Check the serial port for any incoming data
        CheckSerialData()
      Else
        ; There is still have serial data keep coming in, refresh the screen content to reflect the change
        UpdateScreen()
      EndIf
    Case #PB_Event_MoveWindow
      WindowLeft =  WindowX(#MainWindow)
      WindowTop =  WindowY(#MainWindow)
    Case #PB_Event_Menu
      Select EventMenu()  ; To see which menu has been selected
        Case #MenuOpen
          iniFile =AppDirectory + "Instrument.ini"  ; set initial file+path to display          
          FilePattern = "Configuration File (*.ini)|*.ini|All files (*.*)|*.*"
          PatternSelected = 0 ; use the first of the possible patterns as standard
          iniFile = OpenFileRequester("Please choose file to open", iniFile, FilePattern, PatternSelected)
          If iniFile ; file has been selected
            If Loadini(iniFile)
              UpdateStatusLable("Configuration file " + GetFilePart(iniFile) +" is loaded")
              SysUpdateFlag=#False
            Else
              UpdateStatusLable("Failed to load "+GetFilePart(iniFile)+ " configuration. Default setting will be used")
            EndIf
            
            
          Else 
            UpdateStatusLable("Cancel loading new configuration file")
            
          EndIf
        Case #MenuSave
          If Saveini()
            UpdateStatusLable("Configuration file saved")
          Else
            UpdateStatusLable("Failed to save configuration file")
          EndIf          
        Case #MenuSaveAs          
          iniFile =AppDirectory + "Instrument.ini"  ; set initial file+path to display
          FilePattern = "Configuration File (*.ini)|*.ini|All files (*.*)|*.*"
          PatternSelected = 0 ; use the first of the possible patterns as standard
          iniFile = SaveFileRequester("Please choose file to save", iniFile, FilePattern, PatternSelected)
          If iniFile
            If SaveINI(iniFile)
              UpdateStatusLable("Configuration file saved as " + GetFilePart(iniFile))
            Else
              UpdateStatusLable("Failed To save configuration file as " + GetFilePart(iniFile))
            EndIf
          Else
            UpdateStatusLable("Cancel save operation for the configuration file")
          EndIf
        Case #MenuExit
          WindowCloseFlag=#True
        Case #MenuGeneralSetting
          OpenSettingWindow()
        Case #MenuGraphics
          
        Case #MenuConnect
          Connect(@PortCfg)
          UpdateOperationLable()
        Case #MenuDisConnect
          CloseSerialConnection(PortCfg\SerCommPort)
          UpdateOperationLable()
        Case #MenuRecord
          SetGadgetState(#ButtonRecord, #Pressed)
          DisableMenuItem(#MenuMainWindow,#MenuRecord,#Disable)
          DisableMenuItem(#MenuMainWindow,#MenuStop,#Enable)
          CaptureState=#True
          UpdateOperationLable()
        Case #MenuStop
          SetGadgetState(#ButtonRecord,#Unpressed) 
          DisableMenuItem(#MenuMainWindow,#MenuRecord,#Enable)
          DisableMenuItem(#MenuMainWindow,#MenuStop,#Disable)
          CaptureState=#False
          UpdateOperationLable()
          
        Case #MenuAbout ; About
          MessageRequester("About", "Under Construction", 0)
          
        Default
          
      EndSelect
      
    Case #PB_Event_Gadget
      HandleEventGadget()
    Case #PB_Event_Timer
      EventTimerIs = EventTimer()
      If EventTimerIs = #ClockTimer
        CheckTime()
        CheckCount= Mod(CheckCount +1, 5) 
        If CheckCount = 0 ; Means check ~ every 2.5 secs.
          If  IsSerialPort(PortCfg\SerCommPort) 
            DCDStatus = GetSerialPortStatus(PortCfg\SerCommPort,#PB_SerialPort_DCD)
          EndIf
        EndIf
        UpdateScreen()
      EndIf
    Case #PB_Event_CloseWindow
      WindowCloseFlag =#True
  EndSelect
  
EndProcedure
I hope the code shown above could give clues on the problem. I have no clue why this happen. Is it I cannot update gadgets in a thread.

Thanks for giving any idea in advance.

Re: How to solve random invalid memory access in thread?

Posted: Mon Apr 29, 2013 7:38 am
by infratec
Hi,

one first question:

Have you enabled 'thread safe' in the compiler options :?:

Next:
Please reduce the code to a minimum and post a working example
in which the error occure.

I can not run your code snipper above, so I can not help you further.

Bernd

Re: How to solve random invalid memory access in thread?

Posted: Mon Apr 29, 2013 2:51 pm
by calixarene
Thanks for infratec reply.

I have enabled 'thread safe' in the compiler. The data structure and procedure I post are all related to the thread operation that I think is the problem. Since the invalid memory error occurs randomly in procedure calls such as update of gadget content and update of the time stamp, I don't know whether I need to post all of my lengthy code in here. In addition, the code produces problem only when there are certain amount of data coming in from the serial port. The data is input through a null modem cable from another terminal program that read the data from a file. It is hard to run my exiting code without this. You give me a hint that I might need to write another module example for other to help me in this. I might try this if I have time.

I want to make sure that I haven't done something obviously wrong that should not be done in a thread as I am not familiar with thread and pointer in purebasic. I am spoiled by using "ByRef".

I like the small footprint and the fast code that can be generated by purebasic. Though I bought it few years ago, It's a pity that I have not much time to play with it.

Thanks for the people in this forum. I learn a lot from here (I am a background task. :wink: )

Re: How to solve random invalid memory access in thread?

Posted: Mon Apr 29, 2013 5:59 pm
by netmaestro
You`re writing past the end of something is the usual culprit. For debugging purposes you can turn on the purifier in Compiler Options.. Compile-Run and it will tell you next time you run it where the overwrite is happening. From there it`s a matter of adjusting a structure size or allocating a larger block of memory and bob`s your uncle.

Re: How to solve random invalid memory access in thread?

Posted: Tue Apr 30, 2013 10:03 am
by calixarene
Thanks for your suggest netmaestro.

I have enabled the purifier but I don't know how to use it example what will happen.

Currently, I found the problem. It might be because "thread safe" is not enabled yet even I have already enabled in the preference checked box. I have not noticed that there is no (Thread) message in complier log. In fact I have checked several times that preference dialog box has thread safe enabled.

The problem is solved by coping all my code into a new file and save it in a new name. Disabled my Anti-virus (Kaspersky) before complile/run. A (Thread) message in the compiler log is shown up. Everything work fine up to now. I always need to pause Kaspersky before compile/run with purebasic otherwise, the debuger will give a timeout error sometimes.

I don't know whether the thread safe option is not implement in the compile stage is due to the anti-virus or because I have not enable the thread option during my first time saving the program.

Thanks again for the suggestions.