Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

(This was originally posted at StackOverflow on 10/14 but I've copied it here per suggestion by @mikalai)

I'm seeking some second opinions regarding the validity of my test.

In a nutshell, I'm using MMF to simulate a synchronous method call from a WinForms client to a Windows Service. Neither WCF nor Named Pipes is a good fit for this, due to the reasons discussed here.

For my prototype I've created two simple applications--a console app represents the WinForms 'client' and a WinForms app represents the WinService 'server.'

I've had to overcome two main problems: 1) The server's timer fires only once every three seconds, and 2) The OS seems to require at least a full second to acquire and then release a mutex. This second point has got me curious. I'd have thought the process was instant, but apparently I'm mistaken.

To accomodate these two issues I've used Thread.Sleep--two seconds to wait for the server's timer and one second to wait for the mutex handling. Intervals of any less than these cause intermittent read/write synchronization failures. Of course feel free to tweak the code to get your own results.

My test is comprised of a 30,000-iteration loop in the client that 'calls' the 'method' in the server and writes the response to a log file. Currently I'm at iteration ~22,500 without any error.

My question: does anyone see any problems with my test? Given the requirements indicated in my MSDN post (linked above), do my results so far indicate a stable design?

That said, if my architecture can be improved I'd be very interested to hear about it.

Here's the code:

Console (client)

Imports System.IO 
Imports System.IO.MemoryMappedFiles 
Imports System.Threading 
Imports System.Text 
Imports System.Security.Cryptography 

Module Main 
  Sub Main() 
    Dim _ 
      sForward, 
      sReverse, 
      sOutput As String 

    Console.WriteLine("Enter a string to reverse:") 

    For iCount As Integer = 0 To 29999 
      sForward = GetRandomString(7) 
      sReverse = ReverseString(sForward) 
      sOutput = sForward & " => " & sReverse 

      File.AppendAllText("Output.log", sOutput & vbCrLf) 

      Console.WriteLine(sOutput) 
      Thread.Sleep(1000) 
    Next 

    Console.ReadLine() 
  End Sub 

  Private Function GetRandomString(Length As Integer) As String 
    Dim sSeedText As String 
    Dim oBuilder As StringBuilder 
    Dim aBuffer As Byte() 
    Dim oCrypto As RNGCryptoServiceProvider 

    sSeedText = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" 
    oBuilder = New StringBuilder 
    aBuffer = New Byte(Length - 1) {} 
    oCrypto = New RNGCryptoServiceProvider 

    oCrypto.GetNonZeroBytes(aBuffer) 

    For Each bByte As Byte In aBuffer 
      oBuilder.Append(sSeedText.Chars(bByte Mod sSeedText.Length)) 
    Next 

    Return oBuilder.ToString 
  End Function 

  Private Function ReverseString(Data As String) As String 
    Dim lIsOwner As Boolean 
    Dim iLength As Integer 
    Dim aData As Byte() 

    aData = Encoding.Unicode.GetBytes(Data) 
    iLength = aData.Length 

    Using oFile As MemoryMappedFile = MemoryMappedFile.CreateNew("{99EC7026-0059-4D48-99B1-B400BDACBDD8}", 1024) 
      Using oMutex As New Mutex(True, "{35C4F5C6-874B-4536-901A-D5382B7E2B9C}", lIsOwner) 
        ''*===================================================================== 
        '  Wait for the server's Timer.Tick event to fire                      
        ''*===================================================================== 
        Thread.Sleep(2000)
        ''*===================================================================== 

        Using oStream As MemoryMappedViewStream = oFile.CreateViewStream(0, 0) 
          Using oWriter As New BinaryWriter(oStream) 
            oWriter.Write(iLength) 
            oWriter.Write(aData) 
          End Using 
        End Using 

        oMutex.ReleaseMutex() 

        '*=====================================================================' 
        '  Wait for the server to acquire and then release the mutex           '
        '*=====================================================================' 
        Thread.Sleep(1000)
        '*=====================================================================' 

        oMutex.WaitOne() 

        Using oStream As MemoryMappedViewStream = oFile.CreateViewStream(iLength + 4, 0) 
          Using oReader As New BinaryReader(oStream) 
            With oReader 
              aData = .ReadBytes(.ReadInt32) 
            End With 
          End Using 
        End Using 

        oMutex.ReleaseMutex() 
      End Using 
    End Using 

    Return Encoding.Unicode.GetString(aData) 
  End Function 
End Module 

WinForms (server)

'*=============================================================================' 
'                                                                              '
'   Add a ListBox                                                              '
'   Add a Background Worker                                                    '
'   Add a Windows Forms Timer                                                  '
'     Interval = 3000                                                          '
'     Enabled = True                                                           '
'                                                                              '
'*=============================================================================' 

Imports System.IO 
Imports System.IO.MemoryMappedFiles 
Imports System.ComponentModel 
Imports System.Threading 
Imports System.Text 

Public Class Main 
  Private Sub tmrTimer_Tick(Sender As Object, e As EventArgs) Handles tmrTimer.Tick 
    Dim lIsOpen As Boolean 
    Dim oFile As MemoryMappedFile 

    tmrTimer.Stop() 

    Try 
      lIsOpen = True 
      oFile = MemoryMappedFile.OpenExisting("{99EC7026-0059-4D48-99B1-B400BDACBDD8}") 
      bgwWorker.RunWorkerAsync(oFile) 

    Catch ex As FileNotFoundException 
      lIsOpen = False 

    End Try 

    If Not lIsOpen Then 
      tmrTimer.Start() 
    End If 
  End Sub 

  Private Sub bgwWorker_DoWork(Sender As Object, e As DoWorkEventArgs) Handles bgwWorker.DoWork 
    Dim iLength As Integer 
    Dim sData As String 
    Dim aData As Byte() 

    Using oFile As MemoryMappedFile = e.Argument 
      Using oMutex As Mutex = Mutex.OpenExisting("{35C4F5C6-874B-4536-901A-D5382B7E2B9C}") 
        oMutex.WaitOne() 

        Using oStream As MemoryMappedViewStream = oFile.CreateViewStream(0, 0) 
          Using oReader As New BinaryReader(oStream) 
            With oReader 
              aData = .ReadBytes(.ReadInt32) 
            End With 
          End Using 
        End Using 

        sData = Encoding.Unicode.GetString(aData) 
        aData = Encoding.Unicode.GetBytes(New String(sData.Reverse.ToArray)) 
        iLength = aData.Length 

        Using oStream As MemoryMappedViewStream = oFile.CreateViewStream(iLength + 4, 0) 
          Using oWriter As New BinaryWriter(oStream) 
            oWriter.Write(iLength) 
            oWriter.Write(aData) 
          End Using 
        End Using 

        oMutex.ReleaseMutex() 
      End Using 
    End Using 

    e.Result = sData 
  End Sub 

  Private Sub bgwWorker_RunWorkerCompleted(Sender As Object, e As RunWorkerCompletedEventArgs) Handles bgwWorker.RunWorkerCompleted 
    lstArguments.Items.Add(e.Result) 
    tmrTimer.Start() 
  End Sub 
End Class 
share|improve this question
You can also remove you SO question, I suppose. – mikalai Oct 29 '12 at 14:00

1 Answer

  1. I'm suspicious about opening and reopening file again. I have no proof from reading the code, but probably it can be opened once by server at start.

  2. The OS seems to require at least a full second to acquire and then release a mutex.

    Is that what you feel from you program behaviour or did you make some formal tests? That can be just the you case specific.

  3. Replace timers with events. Server, instead of spin-ticking should listen for some kind of RequestWritten event. Then, after making calculations, it should set ResponseWritten event, which would be listened by a client.

  4. That is not clear how client would use server. If there will be several threads, there is no thread-safety. Also, looks like there is no way to send two consecutive messages without reopening the file.

  5. Currently I'm at iteration ~22,500

    What is the failure reason by the way?

share|improve this answer
1. The open/reopen pattern is just for this test. In production the single client makes a single synchronous call to the server only once every few months. But the call must be 100% assured of success, every time. That's the main problem to solve. I'm willing to switch to any design/technology that can accomplish this. – InteXX Oct 29 '12 at 19:54
2. When I lessen or omit the client's 1-second sleep between 'ReleaseMutex()' and 'WaitOne()' the MMF "response" from the server comes back empty. So this is more of an observation than a formal test of that particular detail. – InteXX Oct 29 '12 at 20:02
3. I like the concept, but that would appear to not meet the requirement that the client make a synchronous call to the server. – InteXX Oct 29 '12 at 20:03
4. I see that when I copied/pasted the original SO post I forgot to include the link to the MSDN post wherein I stated the requirements. I've edited my post here to include that link (last word, second paragraph). In short, there's only one client making a single synchronous call once every few months. – InteXX Oct 29 '12 at 20:08
5. In the end I received varied intermittent failures -- mutex not found, empty response data, etc. I'm just about at wit's end trying to come up with a way to do this. – InteXX Oct 29 '12 at 20:11

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.