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