Results 1 to 6 of 6

Thread: [2008] [WORKED AROUND] SyncLock question

  1. #1

    Thread Starter
    Frenzied Member ntg's Avatar
    Join Date
    Sep 2004
    Posts
    1,449

    Exclamation [2008] [WORKED AROUND] SyncLock question

    I have the following two clases:
    VB Code:
    1. ''' <summary>
    2. ''' This class implements a list with keys that expire
    3. ''' after a certain amount of time.
    4. ''' </summary>
    5. ''' <remarks>The timed list holds keys to items stored elsewhere.</remarks>
    6. Public Class TimedList
    7.  
    8.     ''' <summary>
    9.     ''' Event raised when a key has expired.
    10.     ''' </summary>
    11.     ''' <param name="sender"></param>
    12.     ''' <param name="itemKey"></param>
    13.     ''' <remarks></remarks>
    14.     Public Event ItemExpired(ByVal sender As TimedList, ByVal itemKey As String)
    15.  
    16.     ''' <summary>
    17.     ''' List with items.
    18.     ''' </summary>
    19.     ''' <remarks></remarks>
    20.     Protected m_list As SortedList(Of String, TimedItem)
    21.  
    22.     ''' <summary>
    23.     ''' Our pulse timer.
    24.     ''' </summary>
    25.     ''' <remarks></remarks>
    26.     Protected WithEvents m_timer As System.Timers.Timer
    27.  
    28.     ''' <summary>
    29.     ''' Lock object.
    30.     ''' </summary>
    31.     ''' <remarks></remarks>
    32.     Protected Shared m_lock As New Queue
    33.  
    34.     ''' <summary>
    35.     ''' Default class constructor.
    36.     ''' </summary>
    37.     ''' <remarks></remarks>
    38.     Public Sub New()
    39.         m_list = New SortedList(Of String, TimedItem)
    40.     End Sub
    41.  
    42.     ''' <summary>
    43.     ''' Starts our timer.
    44.     ''' </summary>
    45.     ''' <param name="sweepInterval">Sweep interval in seconds.</param>
    46.     ''' <remarks></remarks>
    47.     Public Sub StartTimer(ByVal sweepInterval As Integer)
    48.         m_timer = New Timers.Timer(sweepInterval * 1000)
    49.         m_timer.AutoReset = True
    50.         m_timer.Start()
    51.     End Sub
    52.  
    53.     ''' <summary>
    54.     ''' Stops the timer.
    55.     ''' </summary>
    56.     ''' <remarks></remarks>
    57.     Public Sub StopTimer()
    58.         SyncLock queue.Synchronized(m_lock)
    59.             m_timer.Stop()
    60.             m_timer.Dispose()
    61.             m_timer = Nothing
    62.         End SyncLock
    63.     End Sub
    64.  
    65.     ''' <summary>
    66.     ''' Adds a new key to the timed list.
    67.     ''' </summary>
    68.     ''' <param name="key">Key to add.</param>
    69.     ''' <param name="expirySeconds">Seconds after which the item expires.</param>
    70.     ''' <remarks></remarks>
    71.     Public Sub AddItem(ByVal key As String, ByVal expirySeconds As Integer)
    72.         SyncLock queue.Synchronized(m_lock)
    73.             m_list.Add(key, New TimedItem(key, Now.AddSeconds(expirySeconds)))
    74.         End SyncLock
    75.     End Sub
    76.  
    77.     ''' <summary>
    78.     ''' Removes a key from the list.
    79.     ''' </summary>
    80.     ''' <param name="key"></param>
    81.     ''' <remarks></remarks>
    82.     Public Sub RemoveItem(ByVal key As String)
    83.         SyncLock queue.Synchronized(m_lock)
    84.             m_list.Remove(key)
    85.         End SyncLock
    86.     End Sub
    87.  
    88.     ''' <summary>
    89.     ''' Renews a keys' lifetime.
    90.     ''' </summary>
    91.     ''' <param name="key"></param>
    92.     ''' <param name="expirySeconds"></param>
    93.     ''' <remarks></remarks>
    94.     Public Sub RenewItem(ByVal key As String, ByVal expirySeconds As Integer)
    95.         SyncLock queue.Synchronized(m_lock)
    96.             If m_list.ContainsKey(key) = False Then
    97.                 Throw New Exception("Item with key " + key + " was not found")
    98.             End If
    99.             m_list(key).Expiry = Now.AddSeconds(expirySeconds)
    100.         End SyncLock
    101.     End Sub
    102.  
    103.     ''' <summary>
    104.     ''' Clears all items in the list.
    105.     ''' </summary>
    106.     ''' <remarks></remarks>
    107.     Public Sub Cleanup()
    108.         SyncLock queue.Synchronized(m_lock)
    109.             m_list.Clear()
    110.         End SyncLock
    111.     End Sub
    112.  
    113.     ''' <summary>
    114.     ''' Called when the timer elapses.
    115.     ''' </summary>
    116.     ''' <param name="sender"></param>
    117.     ''' <param name="e"></param>
    118.     ''' <remarks></remarks>
    119.     Private Sub m_timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles m_timer.Elapsed
    120.         m_timer.Stop()
    121.  
    122.         Dim remList As New List(Of String), en As IEnumerator(Of KeyValuePair(Of String, TimedItem)) = Nothing
    123.  
    124.         SyncLock m_lock.SyncRoot
    125.             Try
    126.                 en = m_list.GetEnumerator()
    127.                 en.Reset()
    128.                 While en.MoveNext
    129.                     If en.Current.Value.HasExpired Then
    130.                         Try
    131.                             RaiseEvent ItemExpired(Me, en.Current.Key)
    132.                         Catch ex As Exception
    133.                             Debug.WriteLine("An exception has occurred while notifying caller that an item has expired (" + ex.ToString + ")")
    134.                         End Try
    135.                         remList.Add(en.Current.Key)
    136.                     End If
    137.                 End While
    138.             Catch ex As Exception
    139.                 Debug.WriteLine("+=====================================================================+")
    140.                 Debug.WriteLine("TimedList exception")
    141.                 Debug.WriteLine(ex.ToString)
    142.                 Debug.WriteLine("+=====================================================================+")
    143.             Finally
    144.                 If en IsNot Nothing Then
    145.                     en.Dispose()
    146.                     en = Nothing
    147.                 End If
    148.                 For Each key As String In remList
    149.                     m_list.Remove(key)
    150.                 Next
    151.                 remList = Nothing
    152.             End Try
    153.         End SyncLock
    154.  
    155.         m_timer.Start()
    156.  
    157.     End Sub
    158.  
    159. End Class
    160.  
    161. ''' <summary>
    162. ''' This class is used to represent a timed item.
    163. ''' </summary>
    164. ''' <remarks></remarks>
    165. Public Class TimedItem
    166.     Implements IComparable(Of TimedItem)
    167.  
    168.     ''' <summary>
    169.     ''' Item key.
    170.     ''' </summary>
    171.     ''' <remarks></remarks>
    172.     Protected m_key As String
    173.  
    174.     ''' <summary>
    175.     ''' Item expiry.
    176.     ''' </summary>
    177.     ''' <remarks></remarks>
    178.     Protected m_expiry As DateTime
    179.  
    180.     ''' <summary>
    181.     ''' Get/set the item key.
    182.     ''' </summary>
    183.     ''' <value></value>
    184.     ''' <returns></returns>
    185.     ''' <remarks></remarks>
    186.     Public Property Key() As String
    187.         Get
    188.             Return m_key
    189.         End Get
    190.         Set(ByVal value As String)
    191.             m_key = value
    192.         End Set
    193.     End Property
    194.  
    195.     ''' <summary>
    196.     ''' Get/set the item expiry.
    197.     ''' </summary>
    198.     ''' <value></value>
    199.     ''' <returns></returns>
    200.     ''' <remarks></remarks>
    201.     Public Property Expiry() As DateTime
    202.         Get
    203.             Return m_expiry
    204.         End Get
    205.         Set(ByVal value As DateTime)
    206.             m_expiry = value
    207.         End Set
    208.     End Property
    209.  
    210.     ''' <summary>
    211.     ''' Default class constructor.
    212.     ''' </summary>
    213.     ''' <param name="key">Item key.</param>
    214.     ''' <param name="expiry">Item expiry.</param>
    215.     ''' <remarks></remarks>
    216.     Public Sub New(ByVal key As String, ByVal expiry As DateTime)
    217.         Me.Key = key
    218.         Me.Expiry = expiry
    219.     End Sub
    220.  
    221.     ''' <summary>
    222.     ''' Returns True if the item has expired, False otherwise.
    223.     ''' </summary>
    224.     ''' <returns></returns>
    225.     ''' <remarks></remarks>
    226.     Public Function HasExpired() As Boolean
    227.         Return (DateTime.Compare(Now, m_expiry) >= 0)
    228.     End Function
    229.  
    230.     ''' <summary>
    231.     ''' Implemented to allow for sort/search operations.
    232.     ''' </summary>
    233.     ''' <param name="other">Other instance of TimedItem.</param>
    234.     ''' <returns></returns>
    235.     ''' <remarks></remarks>
    236.     Public Function CompareTo(ByVal other As TimedItem) As Integer Implements System.IComparable(Of TimedItem).CompareTo
    237.         Return String.Compare(Me.Key, other.Key)
    238.     End Function
    239.  
    240. End Class
    The purpose of TimedList is to have a list that holds items that expire after some time. When that happens, the TimedList fires an event to request any cleanup and then removes the TimedItem from its list. The user of TimedList is bound to add and remove items to the list in a dynamic manner so there is a lock object used to make sure that sensitive operations (list addition and enumeration) are performed one at a time. The items of the TimedList are internally stored in a sorted list which has a key to each item.

    The problem I have is beginning to manifest itself when I make heavy use of a TimedList (I have only one instance of it). When I throw at it items that expire after 5 seconds at the rate of 100 per second for two minutes, I get the following exception:

    System.InvalidOperationException: Collection was modified after the enumerator was instantiated.
    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
    at System.Collections.Generic.SortedList`2.Enumerator.MoveNext()
    at RF.Various.TimedList.m_timer_Elapsed(Object sender, ElapsedEventArgs e)
    I currently have code that works around the issue but I'd much prefer to eradicate the real problem. I can't seem to find anything wrong with my use of SyncLock but note that I'm locking on a different object than the sorted list I'm using. Any ideas anyone?
    Last edited by ntg; Aug 14th, 2008 at 02:44 AM.
    "Feel the force...read the source..."
    Utilities: POPFileDebugViewProcess ExplorerWiresharkKeePassUltraVNCPic2Ascii
    .Net tools & open source: DotNetNukelog4NetCLRProfiler
    My open source projects: Thales SimulatorEFT CalculatorSystem Info ReporterVSS2SVNIBAN Functions
    Customer quote: "If the server has a RAID array, why should we bother with backups?"
    Programmer quote: "I never comment my code. Something that is hard to write should be impossible to comprehend."
    Ignorant quote: "I have no respect for universities, as they teach not practicle stuff, and charge money for"

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,222

    Re: [2008] SyncLock question

    The problem is that you're locking on a different object every time, so none of your Synclock blocks is associated with any other. Each time you call Synchronized you're creating a new object. Besides that, you're not supposed to lock on the result of Synchronized. If you read the documentation for the Synchronized method you'll see that in the code example they provide they lock on the Queue's SyncRoot property, which you do correctly in only one place.

    As far as I can tell, the only reason you've declared that Queue is to use it to synchronise access to the SortedList. That's not the way to go. If you want to synchronise access to the SortedList then just create a object to use for locking:
    vb.net Code:
    1. Private syncRoot As New Object
    Now you lock on that syncRoot variable in EVERY SyncLock block so that they're all associated with each other and then only one of them at a time can be entered.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  3. #3

    Thread Starter
    Frenzied Member ntg's Avatar
    Join Date
    Sep 2004
    Posts
    1,449

    Re: [2008] SyncLock question

    Believe it or not, that was done on purpose. After reading the docs, I was left with the understanding that the Synchronized method would guarantee single-thread access when doing anything except enumerations. That's why I've used SyncRoot for that part only.

    I'll give it a go and let you know how it plays out.
    "Feel the force...read the source..."
    Utilities: POPFileDebugViewProcess ExplorerWiresharkKeePassUltraVNCPic2Ascii
    .Net tools & open source: DotNetNukelog4NetCLRProfiler
    My open source projects: Thales SimulatorEFT CalculatorSystem Info ReporterVSS2SVNIBAN Functions
    Customer quote: "If the server has a RAID array, why should we bother with backups?"
    Programmer quote: "I never comment my code. Something that is hard to write should be impossible to comprehend."
    Ignorant quote: "I have no respect for universities, as they teach not practicle stuff, and charge money for"

  4. #4

    Thread Starter
    Frenzied Member ntg's Avatar
    Join Date
    Sep 2004
    Posts
    1,449

    Re: [2008] SyncLock question

    I tried your suggestion, both with a simple and a shared object. Both times I deadlocked. This happens when the TimedList is removing expired resources (m_timer_Elapsed method) and someone tries to add call AddItem at the same millisecond.

    The TimedList is part of a considerably larger project, with an instance of TimedList being used at the deepest level of the solution. The effect is quite profound since everything freezes.

    I'll see if I can create a standalone application that duplicates the problem. Any ideas until then?
    "Feel the force...read the source..."
    Utilities: POPFileDebugViewProcess ExplorerWiresharkKeePassUltraVNCPic2Ascii
    .Net tools & open source: DotNetNukelog4NetCLRProfiler
    My open source projects: Thales SimulatorEFT CalculatorSystem Info ReporterVSS2SVNIBAN Functions
    Customer quote: "If the server has a RAID array, why should we bother with backups?"
    Programmer quote: "I never comment my code. Something that is hard to write should be impossible to comprehend."
    Ignorant quote: "I have no respect for universities, as they teach not practicle stuff, and charge money for"

  5. #5

    Thread Starter
    Frenzied Member ntg's Avatar
    Join Date
    Sep 2004
    Posts
    1,449

    Re: [2008] SyncLock question

    It turns out that it's not that easy to build a standalone executable that can stress the TimedList as the solution that it currently resides in.

    I've fiddled around some more and during one of my tests I came up with a sorted list that had one item with a key of Nothing and a value of Nothing. As far as I know, there's no way to programmatically do that.
    "Feel the force...read the source..."
    Utilities: POPFileDebugViewProcess ExplorerWiresharkKeePassUltraVNCPic2Ascii
    .Net tools & open source: DotNetNukelog4NetCLRProfiler
    My open source projects: Thales SimulatorEFT CalculatorSystem Info ReporterVSS2SVNIBAN Functions
    Customer quote: "If the server has a RAID array, why should we bother with backups?"
    Programmer quote: "I never comment my code. Something that is hard to write should be impossible to comprehend."
    Ignorant quote: "I have no respect for universities, as they teach not practicle stuff, and charge money for"

  6. #6

    Thread Starter
    Frenzied Member ntg's Avatar
    Join Date
    Sep 2004
    Posts
    1,449

    Re: [2008] SyncLock question

    After some more review, I can't seem to find anything wrong with my implementation of your suggestion jmcilhinney.

    The fact remains that at some point I ended up with an item in a sorted list that had a key equal to Nothing. I will go with my original code that works around the problem for now unless there's any other suggestion.
    "Feel the force...read the source..."
    Utilities: POPFileDebugViewProcess ExplorerWiresharkKeePassUltraVNCPic2Ascii
    .Net tools & open source: DotNetNukelog4NetCLRProfiler
    My open source projects: Thales SimulatorEFT CalculatorSystem Info ReporterVSS2SVNIBAN Functions
    Customer quote: "If the server has a RAID array, why should we bother with backups?"
    Programmer quote: "I never comment my code. Something that is hard to write should be impossible to comprehend."
    Ignorant quote: "I have no respect for universities, as they teach not practicle stuff, and charge money for"

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width