Prechádzať zdrojové kódy

SCSITarget now implements a queue for async command execution and use a dedicated I/O thread

Tal Aloni 8 rokov pred
rodič
commit
6df92f3d61

+ 50 - 0
ISCSI/SCSITarget/SCSITarget.cs

@@ -7,15 +7,65 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
+using System.Threading;
+using Utilities;
 
 namespace SCSI
 {
+    public delegate void OnCommandCompleted(SCSIStatusCodeName status, byte[] responseBytes, object task);
+
     public abstract class SCSITarget
     {
+        private class SCSICommand
+        {
+            public byte[] CommandBytes;
+            public LUNStructure LUN;
+            public byte[] Data;
+            public object Task;
+            public OnCommandCompleted OnCommandCompleted;
+        }
+
+        private BlockingQueue<SCSICommand> m_commandQueue = new BlockingQueue<SCSICommand>();
+
         public event EventHandler<StandardInquiryEventArgs> OnStandardInquiry;
 
         public event EventHandler<DeviceIdentificationInquiryEventArgs> OnDeviceIdentificationInquiry;
 
+        public SCSITarget()
+        {
+            Thread workerThread = new Thread(ProcessCommandQueue);
+            workerThread.IsBackground = true;
+            workerThread.Start();
+        }
+
+        public void ProcessCommandQueue()
+        {
+            while (true)
+            {
+                SCSICommand command;
+                bool stopping = !m_commandQueue.TryDequeue(out command);
+                if (stopping)
+                {
+                    return;
+                }
+
+                byte[] responseBytes;
+                SCSIStatusCodeName status = ExecuteCommand(command.CommandBytes, command.LUN, command.Data, out responseBytes);
+                command.OnCommandCompleted(status, responseBytes, command.Task);
+            }
+        }
+
+        public void QueueCommand(byte[] commandBytes, LUNStructure lun, byte[] data, object task, OnCommandCompleted OnCommandCompleted)
+        {
+            SCSICommand command = new SCSICommand();
+            command.CommandBytes = commandBytes;
+            command.LUN = lun;
+            command.Data = data;
+            command.OnCommandCompleted = OnCommandCompleted;
+            command.Task = task;
+            m_commandQueue.Enqueue(command);
+        }
+
         public abstract SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response);
 
         public void NotifyStandardInquiry(object sender, StandardInquiryEventArgs args)

+ 12 - 9
ISCSI/SCSITarget/VirtualSCSITarget.cs

@@ -8,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
+using System.Threading;
 using System.Runtime.InteropServices;
 using DiskAccessLibrary;
 using Utilities;
@@ -17,15 +18,20 @@ namespace SCSI
     public class VirtualSCSITarget : SCSITarget
     {
         private List<Disk> m_disks;
-        private object m_ioLock = new object(); // "In multithreaded applications, a stream must be accessed in a thread-safe way"
 
         public event EventHandler<LogEntry> OnLogEntry;
 
         public VirtualSCSITarget(List<Disk> disks)
         {
             m_disks = disks;
+            Thread workerThread = new Thread(ProcessCommandQueue);
+            workerThread.IsBackground = true;
+            workerThread.Start();
         }
 
+        /// <summary>
+        /// This implementation is not thread-safe.
+        /// </summary>
         public override SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response)
         {
             SCSICommandDescriptorBlock command;
@@ -43,6 +49,9 @@ namespace SCSI
             return ExecuteCommand(command, lun, data, out response);
         }
 
+        /// <summary>
+        /// This implementation is not thread-safe.
+        /// </summary>
         public SCSIStatusCodeName ExecuteCommand(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
         {
             Log(Severity.Verbose, "Executing command: {0}", command.OpCode);
@@ -350,10 +359,7 @@ namespace SCSI
             Log(Severity.Verbose, "LUN {0}: Reading {1} blocks starting from LBA {2}", lun, sectorCount, (long)command.LogicalBlockAddress64);
             try
             {
-                lock (m_ioLock)
-                {
-                    response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
-                }
+                response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
                 return SCSIStatusCodeName.Good;
             }
             catch (ArgumentOutOfRangeException)
@@ -436,10 +442,7 @@ namespace SCSI
             Log(Severity.Verbose, "LUN {0}: Writing {1} blocks starting from LBA {2}", lun, command.TransferLength, (long)command.LogicalBlockAddress64);
             try
             {
-                lock (m_ioLock)
-                {
-                    disk.WriteSectors((long)command.LogicalBlockAddress64, data);
-                }
+                disk.WriteSectors((long)command.LogicalBlockAddress64, data);
                 response = new byte[0];
                 return SCSIStatusCodeName.Good;
             }

+ 8 - 0
ISCSI/Server/ConnectionState.cs

@@ -33,6 +33,14 @@ namespace ISCSI.Server
         public CountdownLatch RunningSCSICommands = new CountdownLatch();
         public BlockingQueue<ISCSIPDU> SendQueue = new BlockingQueue<ISCSIPDU>();
 
+        public void OnCommandCompleted(SCSIStatusCodeName status, byte[] responseBytes, object task)
+        {
+            RunningSCSICommands.Decrement();
+            SCSICommandPDU command = (SCSICommandPDU)task;
+            List<ISCSIPDU> responseList = TargetResponseHelper.PrepareSCSICommandResponse(command, status, responseBytes, ConnectionParameters);
+            SendQueue.Enqueue(responseList);
+        }
+
         public string ConnectionIdentifier
         {
             get

+ 3 - 7
ISCSI/Server/ISCSIServer.cs

@@ -473,15 +473,11 @@ namespace ISCSI.Server
                     {
                         state.RunningSCSICommands.Add(commandsToExecute.Count);
                     }
-                    List<ISCSIPDU> responseList = new List<ISCSIPDU>();
-                    foreach(SCSICommandPDU command in commandsToExecute)
+                    foreach (SCSICommandPDU commandPDU in commandsToExecute)
                     {
-                        Log(Severity.Debug, "[{0}] Executing command: CmdSN: {1}", state.ConnectionIdentifier, command.CmdSN);
-                        List<ISCSIPDU> commandResponseList = TargetResponseHelper.GetSCSICommandResponse(command, state.Target, state.SessionParameters, state.ConnectionParameters);
-                        state.RunningSCSICommands.Decrement();
-                        responseList.AddRange(commandResponseList);
+                        Log(Severity.Debug, "[{0}] Queuing command: CmdSN: {1}", state.ConnectionIdentifier, commandPDU.CmdSN);
+                        state.Target.QueueCommand(commandPDU.CommandDescriptorBlock, commandPDU.LUN, commandPDU.Data, commandPDU, state.OnCommandCompleted);
                     }
-                    state.SendQueue.Enqueue(responseList);
                 }
                 else if (pdu is LoginRequestPDU)
                 {

+ 0 - 8
ISCSI/Server/TargetResponseHelper.cs

@@ -114,14 +114,6 @@ namespace ISCSI.Server
             }
         }
 
-        internal static List<ISCSIPDU> GetSCSICommandResponse(SCSICommandPDU command, SCSITarget target, SessionParameters session, ConnectionParameters connection)
-        {
-            string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
-            byte[] scsiResponse;
-            SCSIStatusCodeName status = target.ExecuteCommand(command.CommandDescriptorBlock, command.LUN, command.Data, out scsiResponse);
-            return PrepareSCSICommandResponse(command, status, scsiResponse, connection);
-        }
-
         internal static List<ISCSIPDU> PrepareSCSICommandResponse(SCSICommandPDU command, SCSIStatusCodeName status, byte[] scsiResponse, ConnectionParameters connection)
         {
             List<ISCSIPDU> responseList = new List<ISCSIPDU>();