public InboxRef(string name, int maxMsgSize) { this.name = name; this.maxMsgSize = maxMsgSize; this.inbox = null; this.emptyBoxEvent = null; }
/// <summary> /// This method initializes a shared memory inbox by creating one if /// it doesn't already exist or opening it if it does exist. Note that /// all successful calls to Open() must be matched with a call to /// <see cref="Close" />. Note that to avoid problems, only one process /// may open an inbox using the SharedMemInbox class. This method is threadsafe. /// </summary> /// <param name="name"> /// Name of the inbox. This can be a maximum of 128 characters and may /// not include the backslash (\) character. The name is case sensitive. /// </param> /// <param name="maxMsgSize"> /// Maximum message size allowed in bytes. Note that this parameter must be /// the same across all instances of SharedMemInbox and SharedMemOutBox /// classes accessing this inbox. /// </param> /// <param name="onReceive"> /// Delegate to be called when a message is placed in the inbox. Note that /// this method will be called on a pool thread. /// </param> public unsafe void Open(string name, int maxMsgSize, SharedMemInboxReceiveDelegate onReceive) { if (name.Length > 128) { throw new ArgumentException("Name exceeds 128 characters.", "name"); } if (name.IndexOfAny(new char[] { '/', '\\' }) != -1) { throw new ArgumentException("Name may not include forward or backslashes."); } if (maxMsgSize <= 0) { throw new ArgumentException("Invalid maximum message size.", "maxMsgSize"); } lock (syncLock) { if (this.sharedMem != null) { return; // Already open } this.maxMsgSize = maxMsgSize; this.onReceive = onReceive; this.onDispatch = new WaitCallback(OnDispatch); this.sharedMem = new SharedMem(); this.sharedMem.Open(name, maxMsgSize + MemHeaderSize, SharedMem.OpenMode.CREATE_OPEN); // Here's what the abbreviations mean: // // LT = LillTek // SMI = SharedMemInBox // NME = NewMessageEvent // EBE = EmptyBoxEvent this.newMsgEvent = new GlobalAutoResetEvent("LT:SMI:NME:" + name); this.emptyBoxEvent = new GlobalAutoResetEvent("LT:SMI:EBE:" + name); this.killThread = false; #if WINFULL this.recvThread = new Thread(new ThreadStart(ReceiveThreadProc)); #else this.recvThread = new CEThread(new ThreadStart(ReceiveThreadProc)); #endif this.recvThread.Start(); byte *p; p = sharedMem.Lock(); *(int *)&p[MaxMsgSizeOffset] = maxMsgSize; *(int *)&p[CurMsgSizeOffset] = 0; p[InboxListeningOffset] = 1; sharedMem.Unlock(); emptyBoxEvent.Set(); } }
/// <summary> /// Forces the lock release, regardless of the current lock count. /// </summary> /// <remarks> /// It is not an error to call this if the lock is not currently held. /// </remarks> public void ReleaseAll() { lock (syncLock) { if (initLock == null) { return; } initLock.Close(); initLock = null; cLock = 0; } }
/// <summary> /// This method closes the inbox. All successful calls to <see cref="Open" /> must /// be matched with a call to Close() to ensure that system resources /// are released in a timely manner. Note that it is OK to call Close() /// even if the shared memory block is not open. This method is threadsafe. /// </summary> public unsafe void Close() { lock (syncLock) { if (sharedMem == null) { return; } byte *p; p = sharedMem.Lock(); // Indicate that we're not listening any more if (p != null) { p[InboxListeningOffset] = 0; sharedMem.Unlock(); } #if WINFULL // Wait up to 5 seconds for the receive thread to stop normally before // forcing the issue. int cWaits = 0; do { killThread = true; // Kill the receive thread newMsgEvent.Set(); cWaits++; if (cWaits == 5) { recvThread.Abort(); } } while (!recvThread.Join(1000)); #else newMsgEvent.Set(); killThread = true; Thread.Sleep(5000); recvThread.Abort(); recvThread.Join(); #endif sharedMem.Close(); // Release unmanaged resources newMsgEvent.Close(); emptyBoxEvent.Close(); sharedMem = null; onReceive = null; onDispatch = null; } }
/// <summary> /// Attempts to acquire the global application lock. /// </summary> /// <remarks> /// <note> /// <see cref="Release" /> should be called promptly when the /// application terminates to release the lock. /// </note> /// </remarks> /// <exception cref="GlobalLockException">Thrown when there's a problem acquiring the lock.</exception> public unsafe void Lock() { EnhancedMemoryStream ms; byte *pMem; byte[] buf; Assembly assembly; string path; lock (syncLock) { if (initLock != null) { cLock++; // The lock is already acquired return; } try { // Use a global shared memory block to enforce the lock. assembly = Assembly.GetEntryAssembly(); if (assembly == null) { assembly = Assembly.GetCallingAssembly(); } path = Helper.StripFileScheme(assembly.CodeBase); ms = new EnhancedMemoryStream(4096); ms.WriteString16(path); initLock = new SharedMem(); initLock.Open("LT.Lock." + appName, 4096, SharedMem.OpenMode.CREATE_OPEN); pMem = initLock.Lock(); if (pMem[0] != 0 || pMem[1] != 0) { buf = new byte[initLock.Size]; for (int i = 0; i < buf.Length; i++) { buf[i] = pMem[i]; } ms = new EnhancedMemoryStream(buf); initLock.Unlock(); initLock.Close(); initLock = null; throw new GlobalLockException("Global lock is already acquired by [{0}].", ms.ReadString16()); } buf = ms.ToArray(); for (int i = 0; i < buf.Length; i++) { pMem[i] = buf[i]; } initLock.Unlock(); } catch (Exception e) { throw new GlobalLockException(e); } } cLock++; }
/// <summary> /// This method transmits the byte message array passed to the inbox. Note that /// you may pass <paramref name="message" /> as <c>null</c>. In this case, the method will simply detect /// whether or not the inbox exists and is able to accept a message transmission. /// </summary> /// <param name="message">The message array.</param> /// <param name="maxWait">The maximum time to wait for the transmission to complete.</param> /// <returns><c>true</c> if the operation was successful.</returns> public unsafe bool Send(byte[] message, TimeSpan maxWait) { // See the comment in SharedMemInbox for a description of the // shared memory block format. byte *p; if (message != null && message.Length > maxMsgSize) { throw new ArgumentException("Message is too large.", "message"); } lock (syncLock) { if (inbox == null) { // Initialize the inbox reference. Return <c>false</c> if the inbox // shared memory does not exist or if there's no SharedMemInbox // listening. try { inbox = new SharedMem(); inbox.Open(name, maxMsgSize, SharedMem.OpenMode.OPEN_ONLY); } catch { inbox = null; return(false); } // Here's what the abbreviations mean: // // LT = LillTek // SMI = SharedMemInBox // NME = NewMessageEvent // EBE = EmptyBoxEvent newMsgEvent = new GlobalAutoResetEvent("LT:SMI:NME:" + name); emptyBoxEvent = new GlobalAutoResetEvent("LT:SMI:EBE:" + name); } // Wait for exclusive access to an empty shared memory block and then // send the message. if (emptyBoxEvent == null || !emptyBoxEvent.WaitOne(maxWait, false)) { // $todo: I really shouldn't have to close the inbox here but for // some reason, the emptyBoxEvent is never set in some // situations (like when a service router starts before the // zone or machine router on the computer). At some point, // I'd like to come back and investigate why this is happening. inbox.Close(); newMsgEvent.Close(); emptyBoxEvent.Close(); inbox = null; newMsgEvent = null; emptyBoxEvent = null; return(false); } if (message == null) { emptyBoxEvent.Set(); return(true); } p = inbox.Lock(); try { int cbMax; if (p[SharedMemInbox.InboxListeningOffset] == 0) { return(false); // There's no inbox listening } cbMax = *(int *)&p[SharedMemInbox.MaxMsgSizeOffset]; if (cbMax != maxMsgSize) { throw new Exception("SharedMemInbox MaxMsgSize mismatch."); } for (int i = 0; i < message.Length; i++) { p[SharedMemInbox.MessageOffset + i] = message[i]; } *(int *)&p[SharedMemInbox.CurMsgSizeOffset] = message.Length; newMsgEvent.Set(); } finally { inbox.Unlock(); } } return(true); }
private bool killThread; // Signal to the background thread to die /// <summary> /// Default constructor. /// </summary> public SharedMemInbox() { sharedMem = null; killThread = false; }