1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.IO;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Runtime.Serialization;
10 using System.Runtime.Serialization.Formatters.Binary;
11 using System.Runtime.InteropServices;
12 using System.Threading;
13 
14 using Microsoft.Win32.SafeHandles;
15 
16 using Microsoft.Build.Framework;
17 using Microsoft.Build.BuildEngine.Shared;
18 
19 namespace Microsoft.Build.BuildEngine
20 {
21     internal enum SharedMemoryType
22     {
23         ReadOnly,
24         WriteOnly
25     }
26 
27     /// <summary>
28     /// The shared memory is used to transmit serialized LocalCallDescriptors.
29     /// These local call descriptors encapsulate commands and data that needs
30     /// to be communicated between the parent and child objects. This enumeration
31     /// is used by the shared memory to mark what kind of LocalCallDescriptor
32     /// object is in the shared memory so it can be correctly deserialized.
33     /// This marker is placed at the front of object in the shared memory.
34     /// Enumeration of LocalCallDescriptor Types
35     /// </summary>
36     internal enum ObjectType
37     {
38         // Has the object been serialized using .net serialization (binary formatter)
39         NetSerialization = 1,
40         // Used to mark that the next int read represents how many bytes are in the
41         // large object which is about to be sent
42         FrameMarker = 2,
43         // Mark the end of the batch in sharedMemory.
44         EndMarker = 3,
45         // Below are the enumeration values are for messages / commands which are
46         // passed between the child and the parent processes
47         PostBuildRequests = 4,
48         PostBuildResult = 5,
49         PostLoggingMessagesToHost = 6,
50         UpdateNodeSettings = 7,
51         RequestStatus = 8,
52         PostStatus = 9,
53         InitializeNode = 10,
54         InitializationComplete = 11,
55         ShutdownNode = 12,
56         ShutdownComplete = 13,
57         PostIntrospectorCommand = 14,
58         GenericSingleObjectReply = 15,
59         PostCacheEntriesToHost = 16,
60         GetCacheEntriesFromHost = 17
61     }
62 
63     /// <summary>
64     /// This class is responsible for providing a communication channel between
65     /// a child process and a parent process. Each process (child or parent) will
66     /// have two SharedMemory class instances, one for reading and one for writing.
67     /// For example, a parent will have one shared memory class to "read" data
68     /// sent from the child and one "write" shared The shared memory communicates
69     /// through named shared memory regions.
70     /// </summary>
71     internal class SharedMemory : IDisposable
72     {
73         #region Construction
74 
SharedMemory()75         private SharedMemory()
76         {
77         }
78 
79         /// <summary>
80         /// Constructor
81         /// </summary>
82         /// <param name="name">
83         /// The name the shared memory will be given, this is combination of node,
84         /// username, admin status, and some other ones,
85         /// see LocalNodeProviderGlobalNames.NodeInputMemoryName for greater detail.
86         /// </param>
87         /// <param name="type">
88         ///  This type determines which lock and stream needs to be instantiated
89         ///  within the shared memory class. For example,
90         ///  read only means, only create a memory stream,
91         ///  a read lock and a backing byte array and a binary reader. A write
92         ///  only type means,  create a memory stream, write lock and a binary writer.
93         ///  This type however does not set the type of the memory mapped section,
94         ///  the memory mapped section itself is created
95         ///  with READWRITE access.
96         ///</param>
97         /// <param name="allowExistingMapping">
98         ///  The shared memory is given a parameter to determine whether or not to
99         ///  reuse an existing mapped memory secion. When the node is first created
100         ///  this is false, however when the shared memory threads are created this
101         ///  is true. We do this because we create the shared memory when the node
102         ///  is created, at this point the there should be no shared memory with the
103         ///  same name. However when we create the reader and writer threads
104         ///  (which happens on node reuse) we want to reuse the memory.
105         ///</param>
SharedMemory(string name, SharedMemoryType type, bool allowExistingMapping)106         internal SharedMemory(string name, SharedMemoryType type, bool allowExistingMapping)
107         {
108             this.type = type;
109 
110             InitializeMemoryMapping(name, allowExistingMapping);
111 
112             writeBytesRemaining = 0;
113             readBytesRemaining = 0;
114             readBytesTotal = 0;
115             largeObjectsQueue = null;
116 
117             // Has the shared memory been properly created and is ready to use
118             if (IsUsable)
119             {
120                 // Setup the structures for either a read only or write only stream
121                 InitializeStreams(type);
122                 try
123                 {
124                     // This could fail if two different administrator accounts try and
125                     // access each others nodes as events and semaphores are protected
126                     // against cross account access
127                     InitializeSynchronization();
128                 }
129                 catch (System.UnauthorizedAccessException)
130                 {
131                     if (writeStream != null)
132                     {
133                         // Closes binary writer and the underlying stream
134                         binaryWriter.Close();
135                     }
136 
137                     if (readStream != null)
138                     {
139                         // Closes binary reader and the underlying stream
140                         binaryReader.Close();
141                     }
142 
143                     NativeMethods.UnmapViewOfFile(pageFileView);
144                     pageFileMapping.Dispose();
145                 }
146             }
147         }
148 
149         /// <summary>
150         /// Creates the shared memory region and map a view to it.
151         /// </summary>
InitializeMemoryMapping(string memoryMapName, bool allowExistingMapping)152         private void InitializeMemoryMapping(string memoryMapName, bool allowExistingMapping)
153         {
154             // Null means use the default security permissions
155             IntPtr pointerToSecurityAttributes = NativeMethods.NullPtr;
156             IntPtr pSDNative = IntPtr.Zero;
157             try
158             {
159                 // Check to see if the user is an administrator, this is done to prevent non
160                 // administrator processes from accessing the shared memory. On a vista machine
161                 // the check does not differentiate beween the application being elevated to have
162                 // administrator rights or the application being started with administrator rights.
163                 // If the user is an administator create a new set of securityAttributes which make
164                 // the shared memory only accessable to administrators.
165                 if (NativeMethods.IsUserAdministrator())
166                 {
167                     NativeMethods.SECURITY_ATTRIBUTES saAttr = new NativeMethods.SECURITY_ATTRIBUTES();
168                     uint pSDLength = 0;
169                     if (!NativeMethods.ConvertStringSecurityDescriptorToSecurityDescriptor(NativeMethods.ADMINONLYSDDL, NativeMethods.SECURITY_DESCRIPTOR_REVISION, ref pSDNative, ref pSDLength))
170                     {
171                         throw new System.ComponentModel.Win32Exception();
172                     }
173 
174                     saAttr.bInheritHandle = 0;
175                     saAttr.nLength = Marshal.SizeOf(typeof(NativeMethods.SECURITY_ATTRIBUTES));
176                     saAttr.lpSecurityDescriptor = pSDNative;
177                     pointerToSecurityAttributes = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.SECURITY_ATTRIBUTES)));
178                     Marshal.StructureToPtr(saAttr, pointerToSecurityAttributes, true);
179                 }
180 
181                // The file mapping has either the default (current user) security permissions or
182                // permissions restricted to only administrator users depending on the check above.
183                // If pointerToSecurityAttributes is null the default permissions are used.
184                this.pageFileMapping =
185                     NativeMethods.CreateFileMapping
186                     (
187                         NativeMethods.InvalidHandle,
188                         pointerToSecurityAttributes,
189                         NativeMethods.PAGE_READWRITE,
190                         0,
191                         size + 4,
192                         memoryMapName
193                     );
194 
195                 // If only new mappings are allowed and the current one has been created by somebody else
196                 // delete the mapping. Note that we would like to compare the GetLastError value against
197                 // ERROR_ALREADY_EXISTS but CLR sometimes overwrites the last error so to be safe we'll
198                 // not reuse the node for any unsuccessful value.
199                 if (!allowExistingMapping && Marshal.GetLastWin32Error() != NativeMethods.ERROR_SUCCESS)
200                 {
201                     if (!pageFileMapping.IsInvalid && !pageFileMapping.IsClosed)
202                     {
203                         NativeMethods.UnmapViewOfFile(pageFileView);
204                         pageFileMapping.Close();
205                     }
206                 }
207             }
208             finally
209             {
210                 NativeMethods.LocalFree(pointerToSecurityAttributes);
211                 NativeMethods.LocalFree(pSDNative);
212             }
213 
214             if (!this.pageFileMapping.IsInvalid && !pageFileMapping.IsClosed)
215             {
216                 // Maps a view of a file mapping into the address space of the calling process so that we can use the
217                 // view to read and write to the shared memory region.
218                 this.pageFileView =
219                     NativeMethods.MapViewOfFile
220                     (
221                         this.pageFileMapping,
222                         NativeMethods.FILE_MAP_ALL_ACCESS, // Give the map read, write, and copy access
223                         0,  // Start mapped view at high order offset 0
224                         0,  // Start mapped view at low order offset 0
225                          // The size of the shared memory plus some extra space for an int
226                          // to write the number of bytes written
227                         (IntPtr)(size + 4)
228                     );
229 
230                 // Check to see if the file view has been created on the fileMapping.
231                 if (this.pageFileView == NativeMethods.NullPtr)
232                 {
233                     // Make the shared memory not usable.
234                     this.pageFileMapping.Close();
235                 }
236                 else
237                 {
238                     this.name = memoryMapName;
239                 }
240             }
241         }
242 
243         /// <summary>
244         /// Initialize the MemoryStreams which will be used to contain the serialized data from the LocalCallDescriptors.
245         /// </summary>
InitializeStreams(SharedMemoryType streamType)246         private void InitializeStreams(SharedMemoryType streamType)
247         {
248             // Initialize the .net binary formatter in case we need to use .net serialization.
249             this.binaryFormatter = new BinaryFormatter();
250             this.loggingTypeCache = new Hashtable();
251 
252             if (streamType == SharedMemoryType.ReadOnly)
253             {
254                 this.readBuffer = new byte[size];
255                 this.readStream = new MemoryStream(this.readBuffer);
256                 this.binaryReader = new BinaryReader(this.readStream);
257                 readLock = new object();
258 
259             }
260             else if (streamType == SharedMemoryType.WriteOnly)
261             {
262                 this.writeStream = new MemoryStream();
263                 writeLock = new object();
264                 this.binaryWriter = new BinaryWriter(this.writeStream);
265             }
266             else
267             {
268                 ErrorUtilities.VerifyThrow(false, "Unknown shared memory type.");
269             }
270 
271         }
272 
273         /// <summary>
274         /// Initialize the synchronization variables which will be used to communicate the status of the shared memory between processes.
275         /// </summary>
InitializeSynchronization()276         private void InitializeSynchronization()
277         {
278             this.unreadBatchCounter = new Semaphore(0, size, this.name + "UnreadBatchCounter");
279             this.fullFlag = new EventWaitHandle(false, EventResetMode.ManualReset, this.name + "FullFlag");
280             this.notFullFlag = new EventWaitHandle(true, EventResetMode.ManualReset, this.name + "NotFullFlag");
281             this.readActionCounter = new Semaphore(0, size + /* for full-flag */ 1, this.name + "ReadActionCounter");
282             // Make sure the count of unread batches is 0
283             while (NumberOfUnreadBatches > 0)
284             {
285                 DecrementUnreadBatchCounter();
286             }
287         }
288 
289         #endregion
290 
291         #region Disposal
292 
Dispose(bool disposing)293         protected virtual void Dispose(bool disposing)
294         {
295             if (!disposed)
296             {
297                 if (IsUsable)
298                 {
299                     NativeMethods.UnmapViewOfFile(pageFileView);
300                     pageFileMapping.Dispose();
301 
302                     unreadBatchCounter.Close();
303                     fullFlag.Close();
304                     notFullFlag.Close();
305                     readActionCounter.Close();
306                 }
307 
308                 if (writeStream != null)
309                 {
310                     // Closes binary writer and the underlying stream
311                     binaryWriter.Close();
312                 }
313 
314                 if (readStream != null)
315                 {
316                     // Closes binary reader and the underlying stream
317                     binaryReader.Close();
318                 }
319 
320                 // Set the sentinel.
321                 disposed = true;
322 
323                 // Suppress finalization of this disposed instance.
324                 if (disposing)
325                 {
326                     GC.SuppressFinalize(this);
327                 }
328             }
329         }
330 
331 
Dispose()332         public void Dispose()
333         {
334             Dispose(true);
335         }
336 
337 
~SharedMemory()338         ~SharedMemory()
339         {
340             Dispose();
341         }
342 
343         #endregion
344 
345         #region Properties
346         /// <summary>
347         ///  Indicates the shared memory region been created and initialized properly.
348         /// </summary>
349         internal bool IsUsable
350         {
351             get
352             {
353                 return !pageFileMapping.IsInvalid &&
354                     !pageFileMapping.IsClosed &&
355                     (pageFileView != NativeMethods.NullPtr);
356             }
357         }
358 
359         /// <summary>
360         /// Returns the readActionCounter as a WaitHandle. This WaitHandle is used
361         /// to notify the SharedMemory reader threads that there is something ready
362         /// in the shared memory to be read. The ReadFlag will remain set as long as
363         /// the number of times the shared memory has been read is less than the
364         /// number of times writer thread has written to the shared memory.
365         /// </summary>
366         internal WaitHandle ReadFlag
367         {
368             get
369             {
370                 return readActionCounter;
371             }
372         }
373 
374         /// <summary>
375         /// Indicates when the SharedMemory is full
376         /// </summary>
377         private bool IsFull
378         {
379             get
380             {
381                 // If the flag is set true is returned
382                 // A timeout of 0 means the WaitOne will time out
383                 // instantly and return false if the flag is not set.
384                 return fullFlag.WaitOne(0, false);
385             }
386         }
387         /// <summary>
388         /// The NumberOfUnreadBatches is the number of "batches" written to shared
389         /// memory which have not been read yet by the ReaderThread. A batch
390         /// contains one or more serialized objects.
391         /// </summary>
392         private int NumberOfUnreadBatches
393         {
394             get
395             {
396                 // Relese the semaphore, this will return the number of times the
397                 // semaphore was entered into. This value reflects the count before
398                 // the release is taken into account.
399                 int numberOfUnreadBatches = unreadBatchCounter.Release();
400                 // Decrement the semaphore to offset the increment used to get the count.
401                 unreadBatchCounter.WaitOne();
402 
403                 return numberOfUnreadBatches;
404             }
405         }
406 
407         #endregion
408 
409         #region Methods
410 
411         /// <summary>
412         /// The shared memory is now full, set the correct synchronization variables to
413         /// inform the reader thread of this situation.
414         /// </summary>
MarkAsFull()415         private void MarkAsFull()
416         {
417             fullFlag.Set();
418             notFullFlag.Reset();
419             readActionCounter.Release();
420         }
421 
422         /// <summary>
423         /// The shared memory is no longer full. Set the correct synchronization variables
424         /// to inform the writer thread of this situation.
425         /// </summary>
MarkAsNotFull()426         private void MarkAsNotFull()
427         {
428             fullFlag.Reset();
429             notFullFlag.Set();
430         }
431 
432         /// <summary>
433         /// A batch is now in the shared memory and is ready to be read by the reader thread.
434         /// </summary>
IncrementUnreadBatchCounter()435         private void IncrementUnreadBatchCounter()
436         {
437             // Release increments a semaphore
438             // http://msdn2.microsoft.com/en-us/library/system.threading.semaphore.aspx
439             unreadBatchCounter.Release();
440             readActionCounter.Release();
441         }
442 
443         /// <summary>
444         /// A batch has just been read out of shared memory.
445         /// </summary>
DecrementUnreadBatchCounter()446         private void DecrementUnreadBatchCounter()
447         {
448             // WaitOne decrements the semaphore
449             unreadBatchCounter.WaitOne();
450         }
451 
452         /// <summary>
453         /// This function write out a set of objects into the the shared buffer.
454         /// In normal operation all the objects in the queue are serialized into
455         /// the buffer followed by an end marker class. If the buffer is not big
456         /// enough to contain a single object the object is broken into into
457         /// multiple buffers as follows - first a frame marker is sent containing
458         /// the size of the serialized object + size of end marker. The reader makes
459         /// sure upon receiving the frame marker that its buffer is large enough
460         /// to contain the object about to be sent. After the frame marker the object
461         /// is sent as a series of buffers until all of it is written out.
462         /// </summary>
463         /// <param name="objectsToWrite"> Queue of objects to be sent (mostly logging messages)</param>
464         /// <param name="objectsToWriteHiPriority">Queue of high priority objects (these are commands and statuses) </param>
465         /// <param name="blockUntilDone"> If true the function will block until both queues are empty</param>
Write(DualQueue<LocalCallDescriptor> objectsToWrite, DualQueue<LocalCallDescriptor> objectsToWriteHiPriority, bool blockUntilDone)466         internal void Write(DualQueue<LocalCallDescriptor> objectsToWrite, DualQueue<LocalCallDescriptor> objectsToWriteHiPriority, bool blockUntilDone)
467         {
468             Debug.Assert(type == SharedMemoryType.WriteOnly, "Should only be calling Write from a writeonly shared memory object");
469 
470             lock (writeLock)
471             {
472                 // Loop as long as there are objects availiable and room in the shared memory.
473                 // If blockUntilDone is set continue to loop until all of the objects in both queues are sent.
474                 while ((objectsToWrite.Count > 0 || objectsToWriteHiPriority.Count > 0) &&
475                        ((blockUntilDone && notFullFlag.WaitOne()) || !IsFull))
476                 {
477                     bool isFull = false;
478                     long writeStartPosition = writeStream.Position;
479                     bool writeEndMarker = false;
480 
481                     // Put as many LocalCallDescriptor objects as possible into the shared memory
482                     while (!isFull && (objectsToWrite.Count > 0 || objectsToWriteHiPriority.Count > 0))
483                     {
484                         long writeResetPosition = writeStream.Position;
485 
486                         DualQueue<LocalCallDescriptor> currentQueue = objectsToWriteHiPriority.Count > 0 ? objectsToWriteHiPriority : objectsToWrite;
487 
488                         // writeBytesRemaining == 0 is when we are currently not sending a multi part object through
489                         // the shared memory
490                         if (writeBytesRemaining == 0)
491                         {
492                             // Serialize the object to the memory stream
493                             SerializeCallDescriptorToStream(currentQueue);
494 
495                             // If the size of the serialized object plus the end marker fits within the shared memory
496                             // dequeue the object as it is going to be sent.
497                             if ((writeStream.Position + sizeof(byte)) <= size)
498                             {
499                                 currentQueue.Dequeue();
500                                 writeEndMarker = true;
501                             }
502                             else
503                             {
504                                 // The serialized object plus the end marker is larger than the shared memory buffer
505                                 // Check if it necessary break down the object into multiple buffers
506                                 // If the memoryStream was empty before trying to serialize the object
507                                 // create a frame marker with the size of the object and send through the shared memory
508                                 if (writeResetPosition == 0)
509                                 {
510                                     // We don't want to switch from low priority to high priority queue in the middle of sending a large object
511                                     // so we make a record of which queue contains the large object
512                                     largeObjectsQueue = currentQueue;
513                                     // Calculate the total number of bytes that needs to be sent
514                                     writeBytesRemaining = (int)(writeStream.Position + sizeof(byte));
515                                     // Send a frame marker out to the reader containing the size of the object
516                                     writeStream.Position = 0;
517 
518                                     // Write the frameMarkerId byte and then the amount of bytes for the large object
519                                     writeStream.WriteByte((byte)ObjectType.FrameMarker);
520                                     binaryWriter.Write((Int32)writeBytesRemaining);
521                                     writeEndMarker = true;
522                                 }
523                                 else
524                                 {
525                                     // Some items were placed in the shared Memory buffer, erase the last one which was too large
526                                     // and say the buffer is full so it can be sent
527                                     writeStream.Position = writeResetPosition;
528                                 }
529                                 isFull = true;
530                             }
531                         }
532                         else
533                         {
534                             if (writeStream.Position == 0)
535                             {
536                                 // Serialize the object which will be split across multiple buffers
537                                 SerializeCallDescriptorToStream(largeObjectsQueue);
538                                 writeStream.WriteByte((byte)ObjectType.EndMarker);
539                             }
540                             break;
541                         }
542                     }
543 
544                     // If a multi-buffer object is being sent and the large object is still larger or equal to the shard memory buffer - send the next chunk of the object
545                     if (writeBytesRemaining != 0 && writeStream.Position >= size)
546                     {
547                         // Set write Length to an entire buffer length  or just the remaining portion
548                         int writeLength = writeBytesRemaining > size ? size : writeBytesRemaining;
549 
550                         //Write the length of the buffer to the memory file
551                         Marshal.WriteInt32((IntPtr)pageFileView, (int)writeLength);
552                         Marshal.Copy
553                         (
554                             writeStream.GetBuffer(), // Source Buffer
555                             (int)(writeStream.Position - writeBytesRemaining), // Start index
556                             (IntPtr)((int)pageFileView + 4), //Destination (+4 because of the int written to the memory file with the write length)
557                             (int)writeLength // Length of bytes to write
558                         );
559 
560                         writeBytesRemaining = writeBytesRemaining - writeLength;
561                         IncrementUnreadBatchCounter();
562 
563                         // Once the object is fully sent - remove it from the queue
564                         if (writeBytesRemaining == 0)
565                         {
566                             largeObjectsQueue.Dequeue();
567                         }
568 
569                         isFull = true;
570                     }
571 
572                     if (writeEndMarker)
573                     {
574                         writeStream.WriteByte((byte)ObjectType.EndMarker);
575                         // Need to verify the WriteInt32 and ReadInt32 are always atomic operations
576                         //writeSizeMutex.WaitOne();
577                         // Write the size of the buffer to send to the memory stream
578                         Marshal.WriteInt32((IntPtr)pageFileView, (int)writeStream.Position);
579                         //writeSizeMutex.ReleaseMutex();
580 
581                         Marshal.Copy
582                         (
583                             writeStream.GetBuffer(), // Buffer
584                             (int)writeStartPosition, // Start Position
585                             (IntPtr)((int)pageFileView + writeStartPosition + 4), // Destination + 4 for the int indicating the size of the data to be copied to the memory file
586                             (int)(writeStream.Position - writeStartPosition) // Length of data to copy to memory file
587                         );
588 
589                         IncrementUnreadBatchCounter();
590                     }
591 
592                     if (isFull)
593                     {
594                         MarkAsFull();
595                         writeStream.SetLength(0);
596                     }
597                 }
598             }
599         }
600 
601         /// <summary>
602         /// Serialize the first object in the queue to the a memory stream which will be copied into shared memory.
603         /// The write stream which is being written to is not the shared memory itself, it is a memory stream from which
604         /// bytes will be copied and placed in the shared memory in the write method.
605         /// </summary>
SerializeCallDescriptorToStream(DualQueue<LocalCallDescriptor> objectsToWrite)606         private void SerializeCallDescriptorToStream(DualQueue<LocalCallDescriptor> objectsToWrite)
607         {
608             // Get the object by peeking at the queue rather than dequeueing the object. This is done
609             // because we only want to dequeue the object when it has completely been put in shared memory.
610             // This may be done right away if the object is small enough to fit in the shared memory or
611             // may happen after a the object is sent as a number of smaller chunks.
612             object objectToWrite = objectsToWrite.Peek();
613             Debug.Assert(objectToWrite != null, "Expect to get a non-null object from the queue");
614             if (objectToWrite is LocalCallDescriptorForPostBuildResult)
615             {
616                 writeStream.WriteByte((byte)ObjectType.PostBuildResult);
617                 ((LocalCallDescriptorForPostBuildResult)objectToWrite).WriteToStream(binaryWriter);
618             }
619             else if (objectToWrite is LocalCallDescriptorForPostBuildRequests)
620             {
621                 writeStream.WriteByte((byte)ObjectType.PostBuildRequests);
622                 ((LocalCallDescriptorForPostBuildRequests)objectToWrite).WriteToStream(binaryWriter);
623             }
624             else if (objectToWrite is LocalCallDescriptorForPostLoggingMessagesToHost)
625             {
626                 writeStream.WriteByte((byte)ObjectType.PostLoggingMessagesToHost);
627                 ((LocalCallDescriptorForPostLoggingMessagesToHost)objectToWrite).WriteToStream(binaryWriter, loggingTypeCache);
628             }
629             else if (objectToWrite is LocalCallDescriptorForInitializeNode)
630             {
631                 writeStream.WriteByte((byte)ObjectType.InitializeNode);
632                 ((LocalCallDescriptorForInitializeNode)objectToWrite).WriteToStream(binaryWriter);
633             }
634             else if (objectToWrite is LocalCallDescriptorForInitializationComplete)
635             {
636                 writeStream.WriteByte((byte)ObjectType.InitializationComplete);
637                 ((LocalCallDescriptorForInitializationComplete)objectToWrite).WriteToStream(binaryWriter);
638             }
639             else if (objectToWrite is LocalCallDescriptorForUpdateNodeSettings)
640             {
641                 writeStream.WriteByte((byte)ObjectType.UpdateNodeSettings);
642                 ((LocalCallDescriptorForUpdateNodeSettings)objectToWrite).WriteToStream(binaryWriter);
643             }
644             else if (objectToWrite is LocalCallDescriptorForRequestStatus)
645             {
646                 writeStream.WriteByte((byte)ObjectType.RequestStatus);
647                 ((LocalCallDescriptorForRequestStatus)objectToWrite).WriteToStream(binaryWriter);
648             }
649             else if (objectToWrite is LocalCallDescriptorForPostingCacheEntriesToHost)
650             {
651                 writeStream.WriteByte((byte)ObjectType.PostCacheEntriesToHost);
652                 ((LocalCallDescriptorForPostingCacheEntriesToHost)objectToWrite).WriteToStream(binaryWriter);
653             }
654             else if (objectToWrite is LocalCallDescriptorForGettingCacheEntriesFromHost)
655             {
656                 writeStream.WriteByte((byte)ObjectType.GetCacheEntriesFromHost);
657                 ((LocalCallDescriptorForGettingCacheEntriesFromHost)objectToWrite).WriteToStream(binaryWriter);
658             }
659             else if (objectToWrite is LocalCallDescriptorForShutdownComplete)
660             {
661                 writeStream.WriteByte((byte)ObjectType.ShutdownComplete);
662                 ((LocalCallDescriptorForShutdownComplete)objectToWrite).WriteToStream(binaryWriter);
663             }
664             else if (objectToWrite is LocalCallDescriptorForShutdownNode)
665             {
666                 writeStream.WriteByte((byte)ObjectType.ShutdownNode);
667                 ((LocalCallDescriptorForShutdownNode)objectToWrite).WriteToStream(binaryWriter);
668             }
669             else if (objectToWrite is LocalCallDescriptorForPostIntrospectorCommand)
670             {
671                 writeStream.WriteByte((byte)ObjectType.PostIntrospectorCommand);
672                 ((LocalCallDescriptorForPostIntrospectorCommand)objectToWrite).WriteToStream(binaryWriter);
673             }
674             else if (objectToWrite is LocalReplyCallDescriptor)
675             {
676                 writeStream.WriteByte((byte)ObjectType.GenericSingleObjectReply);
677                 ((LocalReplyCallDescriptor)objectToWrite).WriteToStream(binaryWriter);
678             }
679             else if (objectToWrite is LocalCallDescriptorForPostStatus)
680             {
681                 writeStream.WriteByte((byte)ObjectType.PostStatus);
682                 ((LocalCallDescriptorForPostStatus)objectToWrite).WriteToStream(binaryWriter);
683             }
684             else
685             {
686                 // If the object is not one of the well known local descriptors, use .net Serialization to serialize the object
687                 writeStream.WriteByte((byte)ObjectType.NetSerialization);
688                 binaryFormatter.Serialize(writeStream, objectToWrite);
689             }
690         }
691 
692         /// <summary>
693         /// This function reads data from the shared memory buffer and returns a list
694         /// of deserialized LocalCallDescriptors or null. The method will return null
695         /// if the object being sent accross is a multi buffer object. Read needs to
696         /// be called multiple times until the entire large object has been recived.
697         /// Once this has happened the large object is deserialized and returned in
698         /// the Ilist. Read is used by the shared memory reader threads in the LocalNode
699         /// (child end) and the LocalNodeProvider(ParentEnd) to read LocalCallDescriptors
700         /// from the shared memory. Read is called from loops in the SharedMemoryReaderThread
701         /// </summary>
Read()702         internal IList Read()
703         {
704             ErrorUtilities.VerifyThrow(type == SharedMemoryType.ReadOnly, "Should only be calling Read from a readonly shared memory object");
705             ArrayList objectsRead = null;
706             int objectId = -1;
707 
708             lock (readLock)
709             {
710                 if (NumberOfUnreadBatches > 0)
711                 {
712                     // The read stream is a memory stream where data read from the shared memory section
713                     // will be copied to. From  this memory stream LocalCallDescriptors are deserialized.
714                     // Stream position may not be 0 if we are reading a multipart object
715                     int readStartPosition = (int)readStream.Position;
716 
717                     // Read the first int from the memory file. This indicates the number of bytes written to
718                     // shared memory by the write method.
719                     int endWritePosition = Marshal.ReadInt32((IntPtr)((int)pageFileView));
720 
721                     // Copy the bytes written into the shared memory section into the readStream memory stream.
722                     Marshal.Copy
723                     (
724                         (IntPtr)((int)pageFileView + 4 + readStream.Position), // Source
725                         readBuffer, //Destination
726                         (int)(readStream.Position + (readBytesTotal - readBytesRemaining)), // Start Index
727                         (int)(endWritePosition - readStream.Position) //Length of data
728                     );
729 
730                     // If a multi buffer object is being read - decrement the bytes remaining
731                     if (readBytesRemaining != 0)
732                     {
733                         readBytesRemaining -= endWritePosition;
734                     }
735 
736                     // If a multi buffer object is not being read (or just completed) - try
737                     // deserializing the data from the buffer into a set of objects
738                     if (readBytesRemaining == 0)
739                     {
740                         objectsRead = new ArrayList();
741 
742                         // Deserialize the object in the read stream to a LocalCallDescriptor. The objectId
743                         // is the "ObjectType" which was written to the head of the object when written to the memory stream.
744                         // It describes which kind of object was serialized
745                         object objectRead = DeserializeFromStream(out objectId);
746 
747                         // Check if the writer wants to sent a multi-buffer object, by checking
748                         // if the top object is a frame marker.
749                         if (readStartPosition == 0)
750                         {
751                             if ((int)ObjectType.FrameMarker == objectId)
752                             {
753                                 int frameSizeInPages = (int)((((int)objectRead) + NativeMethods.PAGE_SIZE)
754                                                         / NativeMethods.PAGE_SIZE);
755 
756                                 // Read the end marker off the stream
757                                 objectId = binaryReader.ReadByte();
758 
759                                 // Allocate a bigger readStream buffer to contain the large object which will be sent if necessary
760                                 if (readBuffer.Length < frameSizeInPages * NativeMethods.PAGE_SIZE)
761                                 {
762                                     // Close the binary reader and the underlying stream before recreating a larger buffer
763                                     binaryReader.Close();
764 
765                                     this.readBuffer = new byte[frameSizeInPages * NativeMethods.PAGE_SIZE];
766                                     this.readStream = new MemoryStream(this.readBuffer);
767                                     this.readStream.Position = 0;
768 
769                                     // ReCreate the reader on the new stream
770                                     binaryReader = new BinaryReader(readStream);
771                                 }
772 
773                                 readBytesRemaining = (int)objectRead;
774                                 readBytesTotal = (int)objectRead;
775                             }
776                             else
777                             {
778                                 readBytesTotal = 0;
779                             }
780                         }
781 
782                         // Deserialized the objects in the read stream and add them into the arrayList as long as
783                         // we did not encounter a frameMarker which says a large object is next or the end marker
784                         // which marks the end of the batch.
785                         while (((int)ObjectType.EndMarker != objectId) && ((int)ObjectType.FrameMarker != objectId))
786                         {
787                             objectsRead.Add(objectRead);
788                             objectRead = DeserializeFromStream(out objectId);
789                         }
790                     }
791 
792                     DecrementUnreadBatchCounter();
793                 }
794                 else
795                 {
796                     MarkAsNotFull();
797                     readStream.Position = 0;
798                 }
799             }
800 
801             return objectsRead;
802         }
803 
804         /// <summary>
805         /// This method first reads the objectId as an int from the stream,
806         /// this int should be found in the "ObjectType" enumeration. This
807         /// objectId informs the method what kind of object should be
808         /// deserialized and returned from the method. The objectId is an
809         /// output parameter. This parameter is also returned so it can be
810         /// used in the read and write methods to determine if
811         /// a frame or end marker was found.
812         /// </summary>
DeserializeFromStream(out int objectId)813         private object DeserializeFromStream(out int objectId)
814         {
815             object objectRead = null;
816             objectId = readStream.ReadByte();
817             switch ((ObjectType)objectId)
818             {
819                 case ObjectType.NetSerialization:
820                     objectRead = binaryFormatter.Deserialize(readStream);
821                     break;
822                 case ObjectType.FrameMarker:
823                     objectRead = binaryReader.ReadInt32();
824                     break;
825                 case ObjectType.PostBuildResult:
826                     objectRead = new LocalCallDescriptorForPostBuildResult();
827                     ((LocalCallDescriptorForPostBuildResult)objectRead).CreateFromStream(binaryReader);
828                     break;
829                 case ObjectType.PostBuildRequests:
830                     objectRead = new LocalCallDescriptorForPostBuildRequests();
831                     ((LocalCallDescriptorForPostBuildRequests)objectRead).CreateFromStream(binaryReader);
832                     break;
833                 case ObjectType.PostLoggingMessagesToHost:
834                     objectRead = new LocalCallDescriptorForPostLoggingMessagesToHost();
835                     ((LocalCallDescriptorForPostLoggingMessagesToHost)objectRead).CreateFromStream(binaryReader, loggingTypeCache);
836                     break;
837                 case ObjectType.InitializeNode:
838                     objectRead = new LocalCallDescriptorForInitializeNode();
839                     ((LocalCallDescriptorForInitializeNode)objectRead).CreateFromStream(binaryReader);
840                     break;
841                 case ObjectType.InitializationComplete:
842                     objectRead = new LocalCallDescriptorForInitializationComplete();
843                     ((LocalCallDescriptorForInitializationComplete)objectRead).CreateFromStream(binaryReader);
844                     break;
845                 case ObjectType.UpdateNodeSettings:
846                     objectRead = new LocalCallDescriptorForUpdateNodeSettings();
847                     ((LocalCallDescriptorForUpdateNodeSettings)objectRead).CreateFromStream(binaryReader);
848                     break;
849                 case ObjectType.RequestStatus:
850                     objectRead = new LocalCallDescriptorForRequestStatus();
851                     ((LocalCallDescriptorForRequestStatus)objectRead).CreateFromStream(binaryReader);
852                     break;
853                 case ObjectType.PostCacheEntriesToHost:
854                     objectRead = new LocalCallDescriptorForPostingCacheEntriesToHost();
855                     ((LocalCallDescriptorForPostingCacheEntriesToHost)objectRead).CreateFromStream(binaryReader);
856                     break;
857                 case ObjectType.GetCacheEntriesFromHost:
858                     objectRead = new LocalCallDescriptorForGettingCacheEntriesFromHost();
859                     ((LocalCallDescriptorForGettingCacheEntriesFromHost)objectRead).CreateFromStream(binaryReader);
860                     break;
861                 case ObjectType.ShutdownComplete:
862                     objectRead = new LocalCallDescriptorForShutdownComplete();
863                     ((LocalCallDescriptorForShutdownComplete)objectRead).CreateFromStream(binaryReader);
864                     break;
865                 case ObjectType.ShutdownNode:
866                     objectRead = new LocalCallDescriptorForShutdownNode();
867                     ((LocalCallDescriptorForShutdownNode)objectRead).CreateFromStream(binaryReader);
868                     break;
869                 case ObjectType.PostIntrospectorCommand:
870                     objectRead = new LocalCallDescriptorForPostIntrospectorCommand(null, null);
871                     ((LocalCallDescriptorForPostIntrospectorCommand)objectRead).CreateFromStream(binaryReader);
872                     break;
873                 case ObjectType.GenericSingleObjectReply:
874                     objectRead = new LocalReplyCallDescriptor();
875                     ((LocalReplyCallDescriptor)objectRead).CreateFromStream(binaryReader);
876                     break;
877                 case ObjectType.PostStatus:
878                     objectRead = new LocalCallDescriptorForPostStatus();
879                     ((LocalCallDescriptorForPostStatus)objectRead).CreateFromStream(binaryReader);
880                     break;
881                 case ObjectType.EndMarker:
882                     return null;
883                 default:
884                     ErrorUtilities.VerifyThrow(false, "Should not be here, ObjectId:" + objectId + "Next:" + readStream.ReadByte());
885                     break;
886             }
887             return objectRead;
888         }
889 
890         /// <summary>
891         /// Reset the state of the shared memory, this is called when the node is
892         /// initialized for the first time or when the node is activated due to node reuse.
893         /// </summary>
Reset()894         internal void Reset()
895         {
896             if (readStream != null)
897             {
898                 readStream.Position = 0;
899             }
900             if (writeStream != null)
901             {
902                 writeStream.SetLength(0);
903                 Marshal.WriteInt32((IntPtr)pageFileView, (int)writeStream.Position);
904             }
905             largeObjectsQueue = null;
906         }
907 
908         #endregion
909 
910         #region Member data
911 
912         private const int size = 100 * 1024;
913         private string name;
914         private SafeFileHandle pageFileMapping;
915         private IntPtr pageFileView;
916 
917         private BinaryFormatter binaryFormatter;
918 
919         // Binary reader and writer used to read and write from the memory streams used to contain the deserialized LocalCallDescriptors before and after they are copied
920         // to and from the shared memory region.
921         private BinaryWriter binaryWriter;
922         private BinaryReader binaryReader;
923 
924         /// <summary>
925         /// Memory stream to contain the deserialized objects before they are sent accross the shared memory region
926         /// </summary>
927         private MemoryStream writeStream;
928 
929         // Backing byte array of the readStream
930         private byte[] readBuffer;
931         private MemoryStream readStream;
932 
933         // The count on a semaphore is decremented each time a thread enters the semaphore,
934         // and incremented when a thread releases the semaphore.
935         // When the count is zero, subsequent requests block until other threads release the semaphore.
936         // A semaphore is considered siginaled when the count > 1 and not siginaled when the count is 0.
937 
938         // unreadBatchCounter is used to track how many batches are remaining to be read from shared memory.
939         private Semaphore unreadBatchCounter;
940 
941         //Used to inform the shared memory reader threads the writer thread has written something in shared memory to read.
942 	//The semaphore is incremented when the shared memory is full and when there is an unreadBatch availiable to be read or the shared memory is full.
943 	//The semaphore is decremented when the shared memory reader thread is about to read from the shared memory.
944         private Semaphore readActionCounter;
945 
946         // Whether or not the shared memory is full
947         private EventWaitHandle fullFlag;
948         private EventWaitHandle notFullFlag;
949 
950         private object writeLock;
951         private object readLock;
952 
953         // How many bytes remain to be written for the large object to be fully written to shared memory
954         private int writeBytesRemaining;
955         // How many bytes remain to be read for the large object to be fully read to shared memory
956         private int readBytesRemaining;
957         // How many bytes is the large object in size
958         private int readBytesTotal;
959 
960         // Have we disposed this object yet;
961         private bool disposed;
962 
963         // Is the memory read only or write only
964         private SharedMemoryType type;
965 
966         // Because we are using reflection to get the writeToStream and readFromStream methods from the classes in the framework assembly we found
967         // we were spending a lot of time reflecting for these methods. The loggingTypeCache, caches the methodInfo for the classes and then look them
968         // up when serializing or deserializing the objects.
969         private Hashtable loggingTypeCache;
970 
971         // Keep a pointer to the queue which contains the large object which is being deserialized. We do this because we want to make sure
972         // after the object is properly sent we dequeue off the correct queue.
973         private DualQueue<LocalCallDescriptor> largeObjectsQueue;
974         #endregion
975     }
976 }
977