├── .gitignore ├── CHANGES.txt ├── ExampleClient ├── ExampleClient.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── ExampleWorker ├── ExampleWorker.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── GearmanSharp.Tests ├── App.config ├── ConfigurationTests.cs ├── GearmanClientTests.cs ├── GearmanConnectionTests.cs ├── GearmanSharp.Tests.csproj ├── GearmanWorkerTests.cs ├── Helpers.cs ├── Properties │ └── AssemblyInfo.cs ├── TestConfiguration.config └── UtilTests.cs ├── GearmanSharp.sln ├── GearmanSharp ├── Configuration │ ├── ClusterConfigurationElement.cs │ ├── ClustersConfigurationElementCollection.cs │ ├── GearmanConfigurationSection.cs │ ├── ServerConfigurationElement.cs │ └── ServersConfigurationElementCollection.cs ├── Examples │ └── Example.cs ├── Exceptions │ ├── GearmanApiException.cs │ ├── GearmanConnectionException.cs │ ├── GearmanException.cs │ ├── GearmanFunctionInternalException.cs │ ├── GearmanServerException.cs │ └── NoServerAvailableException.cs ├── Extensions.cs ├── GearmanClient.cs ├── GearmanClientProtocol.cs ├── GearmanConnection.cs ├── GearmanConnectionFactory.cs ├── GearmanConnectionManager.cs ├── GearmanJob.cs ├── GearmanJobInfo.cs ├── GearmanJobPriority.cs ├── GearmanJobRequest.cs ├── GearmanJobStatus.cs ├── GearmanProtocol.cs ├── GearmanSharp.csproj ├── GearmanThreadedWorker.cs ├── GearmanWorker.cs ├── GearmanWorkerProtocol.cs ├── IGearmanClient.cs ├── IGearmanConnection.cs ├── IGearmanConnectionFactory.cs ├── IGearmanJob.cs ├── ISocket.cs ├── Packets │ ├── Packet.cs │ ├── PacketType.cs │ ├── RequestPacket.cs │ └── ResponsePacket.cs ├── Properties │ └── AssemblyInfo.cs ├── Serializers.cs ├── SocketAdapter.cs ├── Util.cs └── docs │ └── protocol.txt ├── LICENSE.txt ├── README.txt ├── TODO.txt └── lib ├── Newtonsoft.Json.License.txt ├── Newtonsoft.Json.dll ├── Newtonsoft.Json.xml ├── Rhino.Mocks.License.txt ├── Rhino.Mocks.dll ├── nunit.framework.License.txt └── nunit.framework.dll /.gitignore: -------------------------------------------------------------------------------- 1 | *resharper.user 2 | _ReSharper.*/ 3 | [Bb]in/ 4 | [Oo]bj/ 5 | *.suo 6 | *.sln.cache 7 | *.user 8 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.3.2, ??? -- ??? 2 | * Added SignalWorkerThreadToStop and JoinWorkerThread methods to 3 | GearmanThreadedWorker, to allow better handling of the worker 4 | thread. 5 | v0.3.1, 2011-06-27 -- Stopped throwing exceptions from the thread. 6 | * Stopped throwing exceptions from the worker thread, which could 7 | crash a service. 8 | v0.3.0, 2011-06-07 -- Status support. Breaking API improvements. 9 | * Added support for GET_STATUS, STATUS_RES and WORK_STATUS packets. 10 | * GearmanClient.SubmitBackgroundJob now returns an instance of 11 | GearmanJobRequest, which you can use to get the status (you need to 12 | send the GET_STATUS request to the same server as the job is on, 13 | so we store the connection in the GearmanJobRequest). 14 | * Removed all specific classes for the different types of packets, 15 | and added a new class, GearmanProtocol, with static methods for 16 | packing/unpacking the requests/responses. 17 | 18 | v0.2.0, 2011-04-28 -- Breaking API improvements 19 | * Changed IGearmanJob to have an instance of JobAssigment instead of 20 | separate properties for JobHandle and FunctionName, to make the raw 21 | byte[] FunctionArgument available. This is a breaking change. 22 | 23 | * Added protected bool OnJobException(Exception, JobAssignment) to 24 | GearmanWorker to make it possible to handle an exception thrown by a 25 | job function. If it returns true, then the a wrapping 26 | GearmanFunctionInternalException will be thrown. If false, the Work 27 | method will just return false. In GearmanWorker this method returns 28 | true. GearmanThreadedWorker overrides this method and returns false, 29 | to avoid having the work loop stopped. Regardless of what you 30 | return, the worker will disconnect from the server. If you don't 31 | want the disconnect to happen, you need to catch the exception in 32 | the job function. 33 | 34 | v0.1.1, 2010-12-10 -- First real version 35 | 36 | v0.1.0, 2010-10-xx -- First public code 37 | -------------------------------------------------------------------------------- /ExampleClient/ExampleClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {4CB78737-271E-4ABA-97CD-C5D7BECA46EB} 9 | Exe 10 | Properties 11 | ExampleClient 12 | ExampleClient 13 | v3.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | ..\lib\Newtonsoft.Json.dll 37 | 38 | 39 | 40 | 3.5 41 | 42 | 43 | 3.5 44 | 45 | 46 | 3.5 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {8B999000-26B7-4E65-9361-92785B249350} 58 | GearmanSharp 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /ExampleClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json.Linq; 6 | using Twingly.Gearman; 7 | using System.Threading; 8 | 9 | namespace ExampleClient 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | var client = new GearmanClient(); 16 | var host = "smeagol"; 17 | client.AddServer(host, 4730); 18 | client.AddServer(host, 4731); 19 | 20 | CreateBackgroundJobs(client, 10); 21 | //CreateJobs(client, 100); 22 | } 23 | 24 | private static void CreateJobs(GearmanClient client, int jobCount) 25 | { 26 | for (int i = 0; i < jobCount; i++) 27 | { 28 | var result = client.SubmitJob("reverse", String.Format("{0}: Hello World", i), 29 | Serializers.UTF8StringSerialize, Serializers.UTF8StringDeserialize); 30 | Console.WriteLine("Job result: {0}", result); 31 | } 32 | } 33 | 34 | private static void CreateBackgroundJobs(GearmanClient client, int jobCount) 35 | { 36 | for (int i = 0; i < jobCount; i++) 37 | { 38 | var request = client.SubmitBackgroundJob("reverse_with_status", Encoding.UTF8.GetBytes(String.Format("{0}: Hello World", i))); 39 | 40 | GearmanJobStatus jobStatus; 41 | do 42 | { 43 | jobStatus = client.GetStatus(request); 44 | } 45 | while (jobStatus.IsKnown && jobStatus.IsRunning); 46 | 47 | Console.WriteLine("Submitted background job. Handle: {0}", request.JobHandle); 48 | } 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /ExampleClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ExampleClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("ExampleClient")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("948833af-59ad-488d-b420-0a263c7114ce")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ExampleWorker/ExampleWorker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {ABFD3420-B2F2-4463-85FB-062F01EF1533} 9 | Exe 10 | Properties 11 | ExampleWorker 12 | ExampleWorker 13 | v3.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | ..\lib\Newtonsoft.Json.dll 37 | 38 | 39 | 40 | 41 | 3.5 42 | 43 | 44 | 3.5 45 | 46 | 47 | 3.5 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {8B999000-26B7-4E65-9361-92785B249350} 59 | GearmanSharp 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /ExampleWorker/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using Twingly.Gearman; 7 | 8 | namespace ExampleWorker 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | try 15 | { 16 | var worker = new GearmanThreadedWorker(); 17 | var host = "smeagol"; 18 | worker.AddServer(host, 4731); 19 | worker.AddServer(host, 4730); 20 | worker.SetClientId("my-threaded-worker"); 21 | worker.RegisterFunction("reverse", DoReverse, Serializers.UTF8StringDeserialize, Serializers.UTF8StringSerialize); 22 | worker.RegisterFunction("reverse_with_status", DoReverseWithStatus, Serializers.UTF8StringDeserialize, Serializers.UTF8StringSerialize); 23 | 24 | Console.WriteLine("Press enter to start work loop, and press enter again to stop"); 25 | Console.ReadLine(); 26 | 27 | worker.StartWorkLoop(); 28 | 29 | Console.ReadLine(); 30 | 31 | worker.StopWorkLoop(); 32 | 33 | Console.WriteLine("Press enter to quit"); 34 | Console.ReadLine(); 35 | } 36 | catch (Exception ex) 37 | { 38 | Console.WriteLine("Got exception: {0}", ex); 39 | return; 40 | } 41 | 42 | } 43 | 44 | private static void DoReverse(IGearmanJob job) 45 | { 46 | Console.WriteLine("Got job with handle: {0}, function: {1}", job.Info.JobHandle, job.Info.FunctionName); 47 | 48 | var str = job.FunctionArgument; 49 | 50 | var strArray = str.ToCharArray(); 51 | Array.Reverse(strArray); 52 | var reversedStr = new string(strArray); 53 | 54 | Console.WriteLine(" Reversed: {0}", reversedStr); 55 | 56 | job.Complete(reversedStr); 57 | } 58 | 59 | private static void DoReverseWithStatus(IGearmanJob job) 60 | { 61 | Console.WriteLine("Got job with handle: {0}, function: {1}", job.Info.JobHandle, job.Info.FunctionName); 62 | 63 | var str = job.FunctionArgument; 64 | job.SetStatus(0, (uint)str.Length); 65 | var reversedArray = new char[str.Length]; 66 | for (int i = 0; i < str.Length; i++) 67 | { 68 | reversedArray[str.Length - i - 1] = str[i]; 69 | job.SetStatus((uint)i+1, (uint)str.Length); 70 | } 71 | 72 | var reversedStr = new string(reversedArray); 73 | Console.WriteLine(" Reversed: {0}", reversedStr); 74 | 75 | job.Complete(reversedStr); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ExampleWorker/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ExampleWorker")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("ExampleWorker")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("be9d0ebd-290c-4acf-8700-cbadfe1622b4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/ConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using NUnit.Framework; 8 | using Twingly.Gearman.Configuration; 9 | 10 | namespace Twingly.Gearman.Tests 11 | { 12 | [TestFixture] 13 | public class ConfigurationTests 14 | { 15 | private const string TestConfigurationFilename = "TestConfiguration.config"; 16 | 17 | [Test] 18 | public void can_read_configuration() 19 | { 20 | var fileMap = new ExeConfigurationFileMap {ExeConfigFilename = TestConfigurationFilename}; 21 | 22 | var config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 23 | if (!File.Exists(config.FilePath)) 24 | throw new ApplicationException(string.Format("The requested configuration file {0} does not exist!", 25 | config.FilePath)); 26 | 27 | var section = config.GetSection("gearman") as GearmanConfigurationSection; 28 | 29 | Assert.IsNotNull(section); 30 | Assert.IsNotNull(section.Clusters); 31 | Assert.IsNotEmpty(section.Clusters); 32 | 33 | foreach (ClusterConfigurationElement cluster in section.Clusters) 34 | { 35 | Assert.IsNotNull(cluster.Name); 36 | Assert.IsNotEmpty(cluster.Name); 37 | Assert.IsNotNull(cluster.Servers); 38 | Assert.IsNotEmpty(cluster.Servers); 39 | 40 | foreach (ServerConfigurationElement server in cluster.Servers) 41 | { 42 | Assert.IsNotNull(server.Host); 43 | Assert.IsNotEmpty(server.Host); 44 | 45 | // Couldn't get the configuration validation to work. 46 | Assert.Greater(server.Port, 0); 47 | Assert.Less(server.Port, 65535); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/GearmanClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | 7 | namespace Twingly.Gearman.Tests 8 | { 9 | [TestFixture] 10 | public class GearmanClientTests 11 | { 12 | [Test] 13 | public void can_submit_backgroundjob() 14 | { 15 | var client = new GearmanClient(); 16 | client.AddServer(Helpers.TestServerHost, Helpers.TestServerPort); 17 | var jobRequest = client.SubmitBackgroundJob("reverse", Encoding.ASCII.GetBytes("Hello World")); 18 | 19 | Assert.IsNotNull(jobRequest); 20 | Assert.IsNotNull(jobRequest.JobHandle); 21 | } 22 | 23 | [Test] 24 | public void can_fetch_jobstatus() 25 | { 26 | var client = new GearmanClient(); 27 | client.AddServer(Helpers.TestServerHost, Helpers.TestServerPort); 28 | var jobRequest = client.SubmitBackgroundJob("reverse", Encoding.ASCII.GetBytes("Hello World")); 29 | var jobStatus = client.GetStatus(jobRequest); 30 | 31 | Assert.IsNotNull(jobStatus); 32 | // We can't safely assert that jobStatus.IsKnown is true, but it most likely should be. 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /GearmanSharp.Tests/GearmanConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using NUnit.Framework; 7 | using Twingly.Gearman.Packets; 8 | 9 | namespace Twingly.Gearman.Tests 10 | { 11 | [TestFixture] 12 | public class GearmanConnectionTests 13 | { 14 | private string _gearmanHost = Helpers.TestServerHost; 15 | private int _gearmanPort = Helpers.TestServerPort; 16 | 17 | [Test] 18 | public void submitting_background_job_should_generate_job_created_response() 19 | { 20 | var connection = new GearmanConnection(_gearmanHost, _gearmanPort); 21 | connection.Connect(); 22 | 23 | var jobReq = GearmanProtocol.PackRequest(PacketType.SUBMIT_JOB_BG, "reverse", Guid.NewGuid().ToString(), "Hello World"); 24 | 25 | connection.SendPacket(jobReq); 26 | var response = connection.GetNextPacket(); 27 | connection.Disconnect(); 28 | 29 | 30 | Assert.AreEqual(PacketType.JOB_CREATED, response.Type); 31 | Assert.IsNotNull(GearmanProtocol.UnpackJobCreatedResponse(response)); 32 | } 33 | 34 | [Test] 35 | public void should_be_able_to_send_can_do_request() 36 | { 37 | var connection = new GearmanConnection(_gearmanHost, _gearmanPort); 38 | connection.Connect(); 39 | 40 | connection.SendPacket(GearmanProtocol.PackRequest(PacketType.CAN_DO, "reverse")); 41 | // Server won't send any response to CanDo. 42 | connection.Disconnect(); 43 | 44 | 45 | //How do we assert that this worked? 46 | } 47 | 48 | [Test] 49 | public void requesting_job_for_function_without_pending_jobs_should_generate_no_job_response() 50 | { 51 | var connection = new GearmanConnection(_gearmanHost, _gearmanPort); 52 | connection.Connect(); 53 | // tell the server which jobs we can receive, but randomize a function name. 54 | // we want to receive the NoJobResponse 55 | connection.SendPacket(GearmanProtocol.PackRequest(PacketType.CAN_DO, Guid.NewGuid().ToString())); 56 | 57 | 58 | connection.SendPacket(new RequestPacket(PacketType.GRAB_JOB)); 59 | var response = connection.GetNextPacket(); 60 | connection.Disconnect(); 61 | 62 | 63 | Assert.AreEqual(PacketType.NO_JOB, response.Type); 64 | } 65 | 66 | [Test] 67 | public void requesting_job_for_function_with_pending_job_should_generate_job_assign_response() 68 | { 69 | var connection = new GearmanConnection(_gearmanHost, _gearmanPort); 70 | connection.Connect(); 71 | // randomize a function name and argument, so it won't collide with other functions and we can assert on them 72 | var functionName = Guid.NewGuid().ToString(); 73 | var functionArgument = Guid.NewGuid().ToString(); 74 | var jobReq = GearmanProtocol.PackRequest(PacketType.SUBMIT_JOB_BG, functionName, Guid.NewGuid().ToString(), functionArgument); 75 | 76 | connection.SendPacket(jobReq); 77 | var jobCreatedResponse = connection.GetNextPacket(); 78 | var jobHandle = GearmanProtocol.UnpackJobCreatedResponse(jobCreatedResponse); 79 | 80 | Debug.WriteLine(String.Format("Created job with handle '{0}' for function: {1}", jobHandle, functionName)); 81 | connection.SendPacket(GearmanProtocol.PackRequest(PacketType.CAN_DO, functionName)); 82 | 83 | 84 | connection.SendPacket(new RequestPacket(PacketType.GRAB_JOB)); 85 | var response = connection.GetNextPacket(); 86 | connection.Disconnect(); 87 | 88 | 89 | Assert.AreEqual(PacketType.JOB_ASSIGN, response.Type); 90 | var jobAssignment = GearmanProtocol.UnpackJobAssignResponse(response); 91 | Assert.AreEqual(jobHandle, jobAssignment.JobHandle); 92 | Assert.AreEqual(functionName, jobAssignment.FunctionName); 93 | Assert.AreEqual(functionArgument, Encoding.ASCII.GetString(jobAssignment.FunctionArgument)); 94 | } 95 | 96 | [Test] 97 | public void can_submit_job_and_grab_job_and_send_work_complete() 98 | { 99 | var connection = new GearmanConnection(_gearmanHost, _gearmanPort); 100 | connection.Connect(); 101 | // randomize a function name and argument, so it won't collide with other functions and we can assert on them 102 | var functionName = Guid.NewGuid().ToString(); 103 | var functionArgument = Guid.NewGuid().ToString(); 104 | var jobReq = GearmanProtocol.PackRequest(PacketType.SUBMIT_JOB_BG, functionName, Guid.NewGuid().ToString(), functionArgument); 105 | 106 | connection.SendPacket(jobReq); 107 | var jobCreatedResponse = connection.GetNextPacket(); 108 | var jobHandle = GearmanProtocol.UnpackJobCreatedResponse(jobCreatedResponse); 109 | Debug.WriteLine(String.Format("Created job with handle '{0}' for function: {1}", jobHandle, functionName)); 110 | connection.SendPacket(GearmanProtocol.PackRequest(PacketType.CAN_DO, functionName)); 111 | connection.SendPacket(new RequestPacket(PacketType.GRAB_JOB)); 112 | var jobAssignment = GearmanProtocol.UnpackJobAssignResponse(connection.GetNextPacket()); 113 | 114 | // Just return the argument as result 115 | var workCompleteRequest = GearmanProtocol.PackRequest(PacketType.WORK_COMPLETE, jobAssignment.JobHandle, jobAssignment.FunctionArgument); 116 | connection.SendPacket(workCompleteRequest); 117 | 118 | 119 | // will we receive any response from creating the job on the same connection? Seems not, but could it happen? 120 | 121 | 122 | // What can we assert here? That we won't get any more jobs perhaps? 123 | connection.SendPacket(new RequestPacket(PacketType.GRAB_JOB)); 124 | var response = connection.GetNextPacket(); 125 | connection.Disconnect(); 126 | Assert.AreEqual(PacketType.NO_JOB, response.Type); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /GearmanSharp.Tests/GearmanSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {AF8300EC-7C8A-46FF-BC03-886C4ED7ECE2} 9 | Library 10 | Properties 11 | Twingly.Gearman.Tests 12 | Twingly.Gearman.Tests 13 | v3.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | ..\lib\nunit.framework.dll 37 | 38 | 39 | False 40 | ..\lib\Rhino.Mocks.dll 41 | 42 | 43 | 44 | 45 | 3.5 46 | 47 | 48 | 3.5 49 | 50 | 51 | 3.5 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | 72 | 73 | {8B999000-26B7-4E65-9361-92785B249350} 74 | GearmanSharp 75 | 76 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/GearmanWorkerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | using Rhino.Mocks; 7 | using Twingly.Gearman.Packets; 8 | 9 | namespace Twingly.Gearman.Tests 10 | { 11 | [TestFixture] 12 | public class GearmanWorkerTests 13 | { 14 | [Test] 15 | public void will_create_the_correct_gearmanjob() 16 | { 17 | // TODO: This broke when we removed all specific packet classes. 18 | 19 | //var stubJobAssignResponse = MockRepository.GenerateStub(); 20 | //var stubConnection = MockRepository.GenerateStub(); 21 | //var stubConnectionFactory = MockRepository.GenerateStub(); 22 | 23 | //const string functionName = "func"; 24 | //var functionArgument = new byte[3] { 1, 2, 3 }; 25 | //const string jobHandle = "handle"; 26 | 27 | //stubJobAssignResponse.Stub(p => p.JobHandle).Return(jobHandle); 28 | //stubJobAssignResponse.Stub(p => p.FunctionName).Return(functionName); 29 | //stubJobAssignResponse.Stub(p => p.FunctionArgument).Return(functionArgument); 30 | //stubJobAssignResponse.Stub(p => p.Type).Return(PacketType.JOB_ASSIGN); 31 | 32 | //stubConnection.Stub(conn => conn.IsConnected()).Return(true); 33 | //stubConnection.Stub(conn => conn.GetNextPacket()) 34 | // .Return(stubJobAssignResponse); 35 | 36 | //stubConnectionFactory.Stub(x => x.CreateConnection("host", 12345)) 37 | // .IgnoreArguments() 38 | // .Return(stubConnection); 39 | 40 | //GearmanJobFunction func = delegate(IGearmanJob job) { 41 | // Assert.IsNotNull(job); 42 | // Assert.AreEqual(jobHandle, job.Info.JobHandle); 43 | // Assert.AreEqual(functionName, job.Info.FunctionName); 44 | // Assert.AreEqual(functionArgument, job.FunctionArgument); 45 | //}; 46 | 47 | 48 | //var worker = new GearmanWorker {ConnectionFactory = stubConnectionFactory}; 49 | 50 | //worker.AddServer("host", 12345); 51 | //worker.RegisterFunction(functionName, func); 52 | //worker.Work(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using Twingly.Gearman.Configuration; 8 | 9 | namespace Twingly.Gearman.Tests 10 | { 11 | public static class Helpers 12 | { 13 | public static string TestServerHost; 14 | public static int TestServerPort; 15 | 16 | static Helpers() 17 | { 18 | var section = (GearmanConfigurationSection) ConfigurationManager.GetSection("gearman"); 19 | var testCluster = section.Clusters["test"]; 20 | var enumerator = testCluster.Servers.GetEnumerator(); 21 | enumerator.MoveNext(); 22 | var server = (ServerConfigurationElement)enumerator.Current; 23 | TestServerHost = server.Host; 24 | TestServerPort = server.Port; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("GearmanSharp.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Twingly AB")] 12 | [assembly: AssemblyProduct("GearmanSharp")] 13 | [assembly: AssemblyCopyright("Copyright © Twingly AB 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c23b87ec-e796-4e29-bcb0-4fbf91293561")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/TestConfiguration.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /GearmanSharp.Tests/UtilTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | 7 | namespace Twingly.Gearman.Tests 8 | { 9 | [TestFixture] 10 | public class UtilTests 11 | { 12 | [Test] 13 | public void test_split_array() 14 | { 15 | var arr = new byte[] { 1, 2, 3, 0, 4, 5, 0 }; 16 | 17 | var arrs = Util.SplitArray(arr); 18 | 19 | Assert.IsNotNull(arrs); 20 | Assert.IsNotEmpty(arrs); 21 | 22 | var splits = "1230450".Split(new char[] {'0'}); 23 | } 24 | 25 | [Test] 26 | public void split_array_will_return_two_empty_arrays_if_argument_only_contains_the_split_byte() 27 | { 28 | var arr = new byte[] { 0 }; 29 | 30 | var arrs = Util.SplitArray(arr); 31 | 32 | Assert.IsNotNull(arrs); 33 | Assert.AreEqual(2, arrs.Length); 34 | Assert.AreEqual(new byte[] {}, arrs[0]); 35 | Assert.AreEqual(new byte[] {}, arrs[1]); 36 | } 37 | 38 | [Test] 39 | public void split_array_returns_array_with_entire_argument_in_array_if_argument_array_contains_no_split_bytes() 40 | { 41 | var arr = new byte[] { 1, 2, 3 }; 42 | var arrs = Util.SplitArray(arr); 43 | 44 | Assert.IsNotNull(arrs); 45 | Assert.AreEqual(1, arrs.Length); 46 | Assert.AreEqual(arr, arrs[0]); 47 | } 48 | 49 | [Test] 50 | public void split_array_returns_array_with_empty_array_in_it_if_argument_array_is_empty() 51 | { 52 | var arr = new byte[] { }; 53 | var arrs = Util.SplitArray(arr); 54 | 55 | Assert.IsNotNull(arrs); 56 | Assert.AreEqual(1, arrs.Length); 57 | Assert.AreEqual(arr, arrs[0]); 58 | } 59 | 60 | [Test] 61 | [ExpectedException(ExceptionType = typeof(ArgumentNullException))] 62 | public void split_array_throws_if_argument_is_null() 63 | { 64 | var arrs = Util.SplitArray(null); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /GearmanSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearmanSharp.Tests", "GearmanSharp.Tests\GearmanSharp.Tests.csproj", "{AF8300EC-7C8A-46FF-BC03-886C4ED7ECE2}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearmanSharp", "GearmanSharp\GearmanSharp.csproj", "{8B999000-26B7-4E65-9361-92785B249350}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleWorker", "ExampleWorker\ExampleWorker.csproj", "{ABFD3420-B2F2-4463-85FB-062F01EF1533}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleClient", "ExampleClient\ExampleClient.csproj", "{4CB78737-271E-4ABA-97CD-C5D7BECA46EB}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F0C43284-1233-4322-9BF8-2FAF8C6DD821}" 13 | ProjectSection(SolutionItems) = preProject 14 | CHANGES.txt = CHANGES.txt 15 | LICENSE.txt = LICENSE.txt 16 | README.txt = README.txt 17 | TODO.txt = TODO.txt 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {AF8300EC-7C8A-46FF-BC03-886C4ED7ECE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {AF8300EC-7C8A-46FF-BC03-886C4ED7ECE2}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {AF8300EC-7C8A-46FF-BC03-886C4ED7ECE2}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {AF8300EC-7C8A-46FF-BC03-886C4ED7ECE2}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {8B999000-26B7-4E65-9361-92785B249350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {8B999000-26B7-4E65-9361-92785B249350}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {8B999000-26B7-4E65-9361-92785B249350}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {8B999000-26B7-4E65-9361-92785B249350}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {ABFD3420-B2F2-4463-85FB-062F01EF1533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {ABFD3420-B2F2-4463-85FB-062F01EF1533}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {ABFD3420-B2F2-4463-85FB-062F01EF1533}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {ABFD3420-B2F2-4463-85FB-062F01EF1533}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {4CB78737-271E-4ABA-97CD-C5D7BECA46EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {4CB78737-271E-4ABA-97CD-C5D7BECA46EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {4CB78737-271E-4ABA-97CD-C5D7BECA46EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {4CB78737-271E-4ABA-97CD-C5D7BECA46EB}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /GearmanSharp/Configuration/ClusterConfigurationElement.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Twingly.Gearman.Configuration 4 | { 5 | public sealed class ClusterConfigurationElement : ConfigurationElement 6 | { 7 | [ConfigurationProperty("name", IsRequired = true)] 8 | public string Name 9 | { 10 | get { return (string)this["name"]; } 11 | set { this["name"] = value; } 12 | } 13 | 14 | [ConfigurationProperty("servers", IsDefaultCollection = false)] 15 | [ConfigurationCollection(typeof(ServersConfigurationElementCollection))] 16 | public ServersConfigurationElementCollection Servers 17 | { 18 | get { return (ServersConfigurationElementCollection)this["servers"]; } 19 | set { this["servers"] = value; } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /GearmanSharp/Configuration/ClustersConfigurationElementCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Twingly.Gearman.Configuration 4 | { 5 | [ConfigurationCollection(typeof(ClusterConfigurationElement), CollectionType = ConfigurationElementCollectionType.BasicMap)] 6 | public sealed class ClustersConfigurationElementCollection : ConfigurationElementCollection 7 | { 8 | public override ConfigurationElementCollectionType CollectionType 9 | { 10 | get { return ConfigurationElementCollectionType.BasicMap; } 11 | } 12 | 13 | public ClusterConfigurationElement this[int index] 14 | { 15 | get { return (ClusterConfigurationElement)base.BaseGet(index); } 16 | set 17 | { 18 | if (base.BaseGet(index) != null) 19 | { 20 | base.BaseRemoveAt(index); 21 | } 22 | base.BaseAdd(index, value); 23 | } 24 | } 25 | 26 | public new ClusterConfigurationElement this[string name] 27 | { 28 | get { return (ClusterConfigurationElement)base.BaseGet(name); } 29 | } 30 | 31 | protected override ConfigurationElement CreateNewElement() 32 | { 33 | return new ClusterConfigurationElement(); 34 | } 35 | 36 | protected override object GetElementKey(ConfigurationElement element) 37 | { 38 | return ((ClusterConfigurationElement)element).Name; 39 | } 40 | 41 | protected override string ElementName 42 | { 43 | get { return "cluster"; } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /GearmanSharp/Configuration/GearmanConfigurationSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Twingly.Gearman.Configuration 8 | { 9 | public sealed class GearmanConfigurationSection : ConfigurationSection 10 | { 11 | [ConfigurationProperty("clusters", IsDefaultCollection = false)] 12 | [ConfigurationCollection(typeof(ClustersConfigurationElementCollection))] 13 | public ClustersConfigurationElementCollection Clusters 14 | { 15 | get { return (ClustersConfigurationElementCollection)this["clusters"]; } 16 | set { this["clusters"] = value; } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /GearmanSharp/Configuration/ServerConfigurationElement.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Twingly.Gearman.Configuration 4 | { 5 | public sealed class ServerConfigurationElement : ConfigurationElement 6 | { 7 | [ConfigurationProperty("host", IsRequired = true)] 8 | public string Host 9 | { 10 | get { return (string)this["host"]; } 11 | set { this["host"] = value; } 12 | } 13 | 14 | [ConfigurationProperty("port", IsRequired = true)] 15 | //[IntegerValidator(MinValue = 1, MaxValue = 65535)] // couldn't get this to work. 16 | public int Port 17 | { 18 | get { return (int)this["port"]; } 19 | set { this["port"] = value; } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /GearmanSharp/Configuration/ServersConfigurationElementCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Twingly.Gearman.Configuration 4 | { 5 | [ConfigurationCollection(typeof(ServerConfigurationElement), CollectionType = ConfigurationElementCollectionType.BasicMap)] 6 | public sealed class ServersConfigurationElementCollection : ConfigurationElementCollection 7 | { 8 | protected override ConfigurationElement CreateNewElement() 9 | { 10 | return new ServerConfigurationElement(); 11 | } 12 | 13 | protected override object GetElementKey(ConfigurationElement element) 14 | { 15 | var serverElement = (ServerConfigurationElement)element; 16 | return string.Format("{0}:{1}", serverElement.Host, serverElement.Port); 17 | } 18 | 19 | protected override string ElementName 20 | { 21 | get { return "server"; } 22 | } 23 | 24 | public override ConfigurationElementCollectionType CollectionType 25 | { 26 | get { return ConfigurationElementCollectionType.BasicMap; } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /GearmanSharp/Examples/Example.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace Twingly.Gearman.Examples 8 | { 9 | // App.config: 10 | // ----------- 11 | // 12 | // 13 | // 14 | //
15 | // 16 | // 17 | // 18 | // 19 | // 20 | // 21 | // 22 | // 23 | // 24 | // 25 | // 26 | // 27 | 28 | class Example 29 | { 30 | // This example uses the JSON.NET library for JSON serializaion/deserialization 31 | [JsonObject] 32 | public class OEmbed 33 | { 34 | public string Title { get; set; } 35 | 36 | [JsonProperty(PropertyName = "author_name")] 37 | public string AuthorName { get; set; } 38 | 39 | // ... 40 | } 41 | 42 | public void SimpleClient() 43 | { 44 | var client = new GearmanClient("gearmanCluster"); 45 | 46 | var handle = client.SubmitBackgroundJob("reverse", Encoding.ASCII.GetBytes("helloworld")); 47 | } 48 | 49 | public void AdvancedClient() 50 | { 51 | var client = new GearmanClient(); 52 | client.AddServer("gearman.example.com"); 53 | client.AddServer("10.0.0.2", 4730); 54 | 55 | var urls = new List { "http://www.youtube.com/watch?v=abc123456", "http://www.youtube.com/watch?v=xyz9876" }; 56 | 57 | var oembeds = client.SubmitJob, IList>("GetOEmbeds", urls, 58 | Serializers.JsonSerialize>, Serializers.JsonDeserialize>); 59 | } 60 | 61 | public void SimpleWorker() 62 | { 63 | var worker = new GearmanWorker("gearmanCluster"); 64 | worker.RegisterFunction("reverse", ReverseFunction); 65 | 66 | while (/* we should continue working is */ true) 67 | { 68 | worker.Work(); 69 | } 70 | } 71 | 72 | private static void ReverseFunction(IGearmanJob job) 73 | { 74 | var str = Encoding.ASCII.GetString(job.FunctionArgument); 75 | var strArray = str.ToCharArray(); 76 | Array.Reverse(strArray); 77 | var reversedStr = new string(strArray); 78 | job.Complete(Encoding.ASCII.GetBytes(reversedStr)); 79 | } 80 | 81 | public void AdvancedWorker() 82 | { 83 | var worker = new GearmanThreadedWorker(); 84 | worker.AddServer("gearman.example.com"); 85 | worker.AddServer("10.0.0.2", 4730); 86 | worker.SetClientId("my-client"); 87 | 88 | worker.RegisterFunction, IList>("GetOEmbeds", GetOembedsFunction, 89 | Serializers.JsonDeserialize>, Serializers.JsonSerialize>); 90 | 91 | // start worker thread 92 | worker.StartWorkLoop(); 93 | 94 | // do other stuff 95 | 96 | // when it's time to stop 97 | worker.StopWorkLoop(); 98 | } 99 | 100 | public void GetOembedsFunction(IGearmanJob, IList> job) 101 | { 102 | var urls = job.FunctionArgument; 103 | var oembeds = new List(); 104 | 105 | foreach (var url in urls) 106 | { 107 | // ... fetch oEmbeds ... 108 | } 109 | 110 | job.Complete(oembeds); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /GearmanSharp/Exceptions/GearmanApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | 6 | namespace Twingly.Gearman.Exceptions 7 | { 8 | public class GearmanApiException : GearmanException 9 | { 10 | public GearmanApiException() { } 11 | public GearmanApiException(string message) : base(message) { } 12 | public GearmanApiException(string message, Exception innerException) : base(message, innerException) { } 13 | protected GearmanApiException(SerializationInfo info, StreamingContext context) : base(info, context) { } 14 | } 15 | } -------------------------------------------------------------------------------- /GearmanSharp/Exceptions/GearmanConnectionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Runtime.Serialization; 5 | 6 | namespace Twingly.Gearman.Exceptions 7 | { 8 | public class GearmanConnectionException : GearmanException 9 | { 10 | public GearmanConnectionException() { } 11 | public GearmanConnectionException(string message) : base(message) { } 12 | public GearmanConnectionException(string message, Exception innerException) : base(message, innerException) { } 13 | protected GearmanConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } 14 | } 15 | } -------------------------------------------------------------------------------- /GearmanSharp/Exceptions/GearmanException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Runtime.Serialization; 5 | 6 | namespace Twingly.Gearman.Exceptions 7 | { 8 | public class GearmanException : Exception 9 | { 10 | public GearmanException() { } 11 | public GearmanException(string message) : base(message) { } 12 | public GearmanException(string message, Exception innerException) : base(message, innerException) { } 13 | protected GearmanException(SerializationInfo info, StreamingContext context) : base(info, context) { } 14 | } 15 | } -------------------------------------------------------------------------------- /GearmanSharp/Exceptions/GearmanFunctionInternalException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | 7 | namespace Twingly.Gearman.Exceptions 8 | { 9 | /// 10 | /// Represents an exception that occured in a registered job function when processing a Gearman job. 11 | /// 12 | public class GearmanFunctionInternalException : GearmanException 13 | { 14 | public GearmanJobInfo JobInfo { get; set; } 15 | 16 | public GearmanFunctionInternalException(GearmanJobInfo jobAssignment) 17 | { 18 | JobInfo = jobAssignment; 19 | } 20 | 21 | public GearmanFunctionInternalException(GearmanJobInfo jobAssignment, string message) 22 | : base(message) 23 | { 24 | JobInfo = jobAssignment; 25 | } 26 | 27 | public GearmanFunctionInternalException(GearmanJobInfo jobAssignment, string message, Exception innerException) 28 | : base(message, innerException) 29 | { 30 | JobInfo = jobAssignment; 31 | } 32 | 33 | protected GearmanFunctionInternalException(SerializationInfo info, StreamingContext context) 34 | : base(info, context) 35 | { 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /GearmanSharp/Exceptions/GearmanServerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Runtime.Serialization; 5 | 6 | namespace Twingly.Gearman.Exceptions 7 | { 8 | public class GearmanServerException : GearmanException 9 | { 10 | public string ErrorCode { get; set; } 11 | 12 | public GearmanServerException(string errorCode) 13 | { 14 | ErrorCode = errorCode; 15 | } 16 | 17 | public GearmanServerException(string errorCode, string errorText) 18 | : base(errorText) 19 | { 20 | ErrorCode = errorCode; 21 | } 22 | 23 | public GearmanServerException(string errorCode, string errorText, Exception innerException) 24 | : base(errorText, innerException) 25 | { 26 | ErrorCode = errorCode; 27 | } 28 | 29 | protected GearmanServerException(SerializationInfo info, StreamingContext context) : base(info, context) { } 30 | } 31 | } -------------------------------------------------------------------------------- /GearmanSharp/Exceptions/NoServerAvailableException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Twingly.Gearman.Exceptions 5 | { 6 | public class NoServerAvailableException : GearmanException 7 | { 8 | public NoServerAvailableException() { } 9 | public NoServerAvailableException(string message) : base(message) { } 10 | public NoServerAvailableException(string message, Exception innerException) : base(message, innerException) { } 11 | protected NoServerAvailableException(SerializationInfo info, StreamingContext context) : base(info, context) { } 12 | } 13 | } -------------------------------------------------------------------------------- /GearmanSharp/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Twingly.Gearman 7 | { 8 | public static class Extensions 9 | { 10 | 11 | // http://dotnetperls.com/array-slice 12 | /// 13 | /// Get the array slice between the two indexes. 14 | /// ... Inclusive for start index, exclusive for end index. 15 | /// 16 | public static T[] Slice(this T[] source, int start, int end) 17 | { 18 | // Handles negative ends. 19 | if (end < 0) 20 | { 21 | end = source.Length + end; 22 | } 23 | int len = end - start; 24 | 25 | // Return new array. 26 | T[] res = new T[len]; 27 | for (int i = 0; i < len; i++) 28 | { 29 | res[i] = source[i + start]; 30 | } 31 | return res; 32 | } 33 | 34 | // http://stackoverflow.com/questions/1287567/c-is-using-random-and-orderby-a-good-shuffle-algorithm 35 | public static IEnumerable Shuffle(this IEnumerable source, Random rng) 36 | { 37 | T[] elements = source.ToArray(); 38 | // Note i > 0 to avoid final pointless iteration 39 | for (int i = elements.Length - 1; i > 0; i--) 40 | { 41 | // Swap element "i" with a random earlier element it (or itself) 42 | int swapIndex = rng.Next(i + 1); 43 | yield return elements[swapIndex]; 44 | elements[swapIndex] = elements[i]; 45 | // we don't actually perform the swap, we can forget about the 46 | // swapped element because we already returned it. 47 | } 48 | 49 | // there is one item remaining that was not returned - we return it now 50 | yield return elements[0]; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /GearmanSharp/GearmanClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using Twingly.Gearman.Configuration; 6 | using Twingly.Gearman.Exceptions; 7 | 8 | namespace Twingly.Gearman 9 | { 10 | public class GearmanClient : GearmanConnectionManager, IGearmanClient 11 | { 12 | public GearmanClient() 13 | { 14 | } 15 | 16 | public GearmanClient(string clusterName) 17 | : base(clusterName) 18 | { 19 | } 20 | 21 | public GearmanClient(ClusterConfigurationElement clusterConfiguration) 22 | : base(clusterConfiguration) 23 | { 24 | } 25 | 26 | public GearmanJobStatus GetStatus(GearmanJobRequest jobRequest) 27 | { 28 | return new GearmanClientProtocol(jobRequest.Connection).GetStatus(jobRequest.JobHandle); 29 | } 30 | 31 | public byte[] SubmitJob(string functionName, byte[] functionArgument) 32 | { 33 | return SubmitJob(functionName, functionArgument, Guid.NewGuid().ToString(), GearmanJobPriority.Normal); 34 | } 35 | 36 | public byte[] SubmitJob(string functionName, byte[] functionArgument, string uniqueId, GearmanJobPriority priority) 37 | { 38 | return SubmitJob(functionName, functionArgument, Guid.NewGuid().ToString(), GearmanJobPriority.Normal, 39 | data => (data), data => (data)); 40 | } 41 | 42 | public TResult SubmitJob(string functionName, TArg functionArgument, 43 | DataSerializer argumentSerializer, DataDeserializer resultDeserializer) 44 | where TArg : class 45 | where TResult : class 46 | { 47 | return SubmitJob(functionName, functionArgument, Guid.NewGuid().ToString(), GearmanJobPriority.Normal, 48 | argumentSerializer, resultDeserializer); 49 | } 50 | 51 | public TResult SubmitJob(string functionName, TArg functionArgument, string uniqueId, GearmanJobPriority priority, 52 | DataSerializer argumentSerializer, DataDeserializer resultDeserializer) 53 | where TArg : class 54 | where TResult : class 55 | { 56 | if (argumentSerializer == null) 57 | throw new ArgumentNullException("argumentSerializer"); 58 | 59 | if (resultDeserializer == null) 60 | throw new ArgumentNullException("resultDeserializer"); 61 | 62 | var functionArgumentBytes = argumentSerializer(functionArgument); // Do this before calling SendClientCommand. 63 | var result = SendClientCommand(protocol => protocol.SubmitJob( 64 | functionName, 65 | functionArgumentBytes, 66 | uniqueId, 67 | priority)); 68 | return result == null ? null : resultDeserializer(result); 69 | } 70 | 71 | public GearmanJobRequest SubmitBackgroundJob(string functionName, byte[] functionArgument) 72 | { 73 | return SubmitBackgroundJob(functionName, functionArgument, CreateRandomUniqueId(), GearmanJobPriority.Normal); 74 | } 75 | 76 | public GearmanJobRequest SubmitBackgroundJob(string functionName, byte[] functionArgument, string uniqueId, GearmanJobPriority priority) 77 | { 78 | return SubmitBackgroundJob(functionName, functionArgument, uniqueId, GearmanJobPriority.Normal, data => (data)); 79 | } 80 | 81 | public GearmanJobRequest SubmitBackgroundJob(string functionName, TArg functionArgument, 82 | DataSerializer argumentSerializer) 83 | where TArg : class 84 | { 85 | return SubmitBackgroundJob(functionName, functionArgument, CreateRandomUniqueId(), GearmanJobPriority.Normal, argumentSerializer); 86 | } 87 | 88 | public GearmanJobRequest SubmitBackgroundJob(string functionName, TArg functionArgument, string uniqueId, GearmanJobPriority priority, 89 | DataSerializer argumentSerializer) 90 | where TArg : class 91 | { 92 | if (argumentSerializer == null) 93 | throw new ArgumentNullException("argumentSerializer"); 94 | 95 | var functionArgumentBytes = argumentSerializer(functionArgument); // Do this before calling SendClientCommand. 96 | return SendClientCommand(protocol => SubmitBackgroundJob(protocol, functionName, functionArgumentBytes, uniqueId, priority)); 97 | } 98 | 99 | private static GearmanJobRequest SubmitBackgroundJob(GearmanClientProtocol protocol, string functionName, byte[] functionArgument, 100 | string uniqueId, GearmanJobPriority priority) 101 | { 102 | var jobHandle = protocol.SubmitBackgroundJob( 103 | functionName, 104 | functionArgument, 105 | uniqueId, 106 | priority); 107 | return new GearmanJobRequest(protocol.Connection, jobHandle); 108 | } 109 | 110 | protected T SendClientCommand(Func commandFunc) 111 | { 112 | foreach (var connection in GetAliveConnections()) 113 | { 114 | try 115 | { 116 | return commandFunc(new GearmanClientProtocol(connection)); 117 | } 118 | catch (GearmanConnectionException) 119 | { 120 | connection.MarkAsDead(); 121 | } 122 | } 123 | 124 | throw new NoServerAvailableException("Failed to send command, no job server available"); 125 | } 126 | 127 | private static string CreateRandomUniqueId() 128 | { 129 | // Guid with format "N" should be max 32 chars long (it won't write the hyphens). 130 | // We only really need something random here, so it's not that important that we use Guid really. 131 | // http://msdn.microsoft.com/en-us/library/97af8hh4.aspx 132 | return Guid.NewGuid().ToString("N"); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /GearmanSharp/GearmanClientProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using Twingly.Gearman.Exceptions; 7 | using Twingly.Gearman.Packets; 8 | 9 | namespace Twingly.Gearman 10 | { 11 | public class GearmanJobData 12 | { 13 | public string JobHandle { get; protected set; } 14 | public byte[] Data { get; protected set; } 15 | 16 | public GearmanJobData(string jobHandle, byte[] data) 17 | { 18 | JobHandle = jobHandle; 19 | Data = data; 20 | } 21 | } 22 | 23 | public class GearmanClientProtocol : GearmanProtocol 24 | { 25 | public GearmanClientProtocol(IGearmanConnection connection) 26 | : base(connection) 27 | { 28 | } 29 | 30 | public string SubmitBackgroundJob(string functionName, byte[] functionArgument, string uniqueId, GearmanJobPriority priority) 31 | { 32 | return SubmitJob(functionName, functionArgument, true, uniqueId, priority); 33 | } 34 | 35 | private string SubmitJob(string functionName, byte[] functionArgument, bool background, string uniqueId, GearmanJobPriority priority) 36 | { 37 | if (functionName == null) 38 | throw new ArgumentNullException("functionName"); 39 | 40 | Connection.SendPacket(PackRequest( 41 | GetSubmitJobType(priority, background), 42 | functionName, 43 | uniqueId ?? "", 44 | functionArgument ?? new byte[0])); 45 | var response = Connection.GetNextPacket(); 46 | 47 | switch (response.Type) 48 | { 49 | case PacketType.JOB_CREATED: 50 | return UnpackJobCreatedResponse(response); 51 | case PacketType.ERROR: 52 | throw UnpackErrorReponse(response); 53 | default: 54 | throw new GearmanApiException("Got unknown packet from server"); 55 | } 56 | } 57 | 58 | private static PacketType GetSubmitJobType(GearmanJobPriority priority, bool background) 59 | { 60 | switch (priority) 61 | { 62 | case GearmanJobPriority.High: 63 | return background ? PacketType.SUBMIT_JOB_HIGH_BG : PacketType.SUBMIT_JOB_HIGH; 64 | case GearmanJobPriority.Normal: 65 | return background ? PacketType.SUBMIT_JOB_BG : PacketType.SUBMIT_JOB; 66 | case GearmanJobPriority.Low: 67 | return background ? PacketType.SUBMIT_JOB_LOW_BG : PacketType.SUBMIT_JOB_LOW; 68 | default: 69 | throw new GearmanApiException("Unknown priority and background combination for SubmitJobRequest"); 70 | } 71 | } 72 | 73 | public byte[] SubmitJob(string functionName, byte[] functionArgument, string uniqueId, GearmanJobPriority priority) 74 | { 75 | var jobHandle = SubmitJob(functionName, functionArgument, false, uniqueId, priority); 76 | 77 | var result = new List(); 78 | var workDone = false; 79 | while (!workDone) 80 | { 81 | var response = Connection.GetNextPacket(); 82 | 83 | // TODO: Check that we received a response for/with the same job handle? 84 | 85 | switch (response.Type) 86 | { 87 | case PacketType.WORK_FAIL: 88 | // Do what? Return null? (should not throw) 89 | return null; 90 | case PacketType.WORK_COMPLETE: 91 | var workComplete = UnpackWorkCompleteResponse(response); 92 | result.AddRange(workComplete.Data); 93 | workDone = true; 94 | break; 95 | case PacketType.WORK_DATA: 96 | var workData = UnpackWorkDataResponse(response); 97 | result.AddRange(workData.Data); 98 | break; 99 | case PacketType.WORK_WARNING: 100 | case PacketType.WORK_STATUS: 101 | case PacketType.WORK_EXCEPTION: 102 | // TODO: Do what? 103 | break; 104 | case PacketType.ERROR: 105 | throw UnpackErrorReponse(response); 106 | default: 107 | throw new GearmanApiException("Got unknown packet from server"); 108 | } 109 | } 110 | 111 | return result.ToArray(); 112 | } 113 | 114 | public GearmanJobStatus GetStatus(string jobHandle) 115 | { 116 | Connection.SendPacket(new RequestPacket(PacketType.GET_STATUS, Encoding.UTF8.GetBytes(jobHandle))); 117 | var response = Connection.GetNextPacket(); 118 | 119 | switch (response.Type) 120 | { 121 | case PacketType.STATUS_RES: 122 | return UnpackStatusResponse(response); 123 | case PacketType.ERROR: 124 | throw UnpackErrorReponse(response); 125 | default: 126 | throw new GearmanApiException("Got unknown packet from server"); 127 | } 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using Twingly.Gearman.Exceptions; 7 | using Twingly.Gearman.Packets; 8 | 9 | namespace Twingly.Gearman 10 | { 11 | public class GearmanConnection : IGearmanConnection 12 | { 13 | public const int DEFAULT_SEND_TIMEOUT_MILLISECONDS = 3*1000; 14 | public const int DEFAULT_RECEIVE_TIMEOUT_MILLISECONDS = 60*1000; 15 | 16 | private readonly TimeSpan _deadServerRetryInterval = TimeSpan.FromSeconds(10); // TODO: make configurable 17 | 18 | private ISocket _socket; 19 | private bool _isDead; 20 | private DateTime _nextRetry; 21 | 22 | /// 23 | /// The send timeout is used to determine how long the client should wait for data to be sent. 24 | /// and received from the server, specified in milliseconds. The default value is DEFAULT_SEND_TIMEOUT_MILLISECONDS. 25 | /// 26 | public int SendTimeout { get; set; } 27 | 28 | /// 29 | /// The receive timeout is used to determine how long the client should wait for data to be received from the server, 30 | /// specified in milliseconds. The default value is DEFAULT_RECEIVE_TIMEOUT_MILLISECONDS. 31 | /// 32 | public int ReceiveTimeout { get; set; } 33 | 34 | public string Host { get; set; } 35 | public int Port { get; set; } 36 | 37 | public GearmanConnection(string host, int port) 38 | { 39 | if (host == null) 40 | throw new ArgumentNullException("host"); 41 | 42 | Host = host; 43 | Port = port; 44 | SendTimeout = DEFAULT_SEND_TIMEOUT_MILLISECONDS; 45 | ReceiveTimeout = DEFAULT_RECEIVE_TIMEOUT_MILLISECONDS; 46 | _isDead = false; 47 | } 48 | 49 | public bool IsDead() 50 | { 51 | if (_isDead && DateTime.Now >= _nextRetry) 52 | _isDead = false; 53 | 54 | return _isDead; 55 | } 56 | 57 | public void MarkAsDead() 58 | { 59 | Disconnect(); 60 | _isDead = true; 61 | _nextRetry = DateTime.Now + _deadServerRetryInterval; 62 | } 63 | 64 | public void Connect() 65 | { 66 | if (IsConnected()) 67 | return; 68 | 69 | Close(); 70 | 71 | try 72 | { 73 | _socket = new SocketAdapter(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) 74 | { 75 | NoDelay = true, 76 | ReceiveTimeout = ReceiveTimeout, 77 | SendTimeout = SendTimeout 78 | }); 79 | 80 | _socket.Connect(Host, Port); 81 | } 82 | catch (Exception ex) 83 | { 84 | throw new GearmanConnectionException("Could not connect", ex); 85 | } 86 | 87 | if (!_socket.Connected) 88 | { 89 | throw new GearmanConnectionException("Socket not connected"); 90 | } 91 | 92 | _isDead = false; 93 | } 94 | 95 | public void Disconnect() 96 | { 97 | Close(); 98 | } 99 | 100 | public void SendPacket(RequestPacket p) 101 | { 102 | try 103 | { 104 | _socket.Send(p.ToByteArray()); 105 | } 106 | catch (Exception e) 107 | { 108 | new GearmanConnectionException("Unable to send packet", e); 109 | } 110 | } 111 | 112 | public IResponsePacket GetNextPacket() 113 | { 114 | var header = new byte[12]; 115 | var packetMagic = new byte[4]; 116 | byte[] packetData; 117 | try 118 | { 119 | _socket.Receive(header, 12, SocketFlags.None); 120 | Array.Copy(header, 0, packetMagic, 0, 4); 121 | 122 | if (!packetMagic.SequenceEqual(ResponsePacket.Magic)) 123 | throw new GearmanApiException("Response packet magic does not match"); 124 | 125 | var packetType = (PacketType)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header, 4)); 126 | int packetSize = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(header, 8)); 127 | 128 | packetData = new byte[packetSize]; 129 | if (packetSize > 0) 130 | { 131 | int bytesRead = 0; 132 | do 133 | { 134 | bytesRead += _socket.Receive(packetData, bytesRead, packetSize - bytesRead, SocketFlags.None); 135 | } while (bytesRead < packetSize); 136 | } 137 | 138 | return new ResponsePacket(packetType, packetData); 139 | } 140 | catch (Exception e) 141 | { 142 | throw new GearmanConnectionException("Error reading data from socket", e); 143 | } 144 | } 145 | 146 | private void Close() 147 | { 148 | if (_socket != null) 149 | { 150 | try 151 | { 152 | _socket.Shutdown(); 153 | _socket.Close(); 154 | } 155 | catch (Exception) 156 | { 157 | //logger.Error("Error shutting down and closing socket: " + EndPoint, e); 158 | } 159 | finally 160 | { 161 | _socket = null; 162 | } 163 | } 164 | } 165 | /// 166 | /// Checks if the underlying socket and stream is connected and available. 167 | /// 168 | public bool IsConnected() 169 | { 170 | return _socket != null && _socket.Connected; 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace Twingly.Gearman 6 | { 7 | public class GearmanConnectionFactory : IGearmanConnectionFactory 8 | { 9 | public IGearmanConnection CreateConnection(string host, int port) 10 | { 11 | if (host == null) 12 | throw new ArgumentNullException("host"); 13 | 14 | return new GearmanConnection(host, port); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanConnectionManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using Twingly.Gearman.Configuration; 7 | using Twingly.Gearman.Exceptions; 8 | 9 | namespace Twingly.Gearman 10 | { 11 | public abstract class GearmanConnectionManager : IDisposable 12 | { 13 | private const int _DEFAULT_PORT = 4730; 14 | 15 | private readonly IList _connections; 16 | private IGearmanConnectionFactory _connectionFactory; 17 | 18 | public IGearmanConnectionFactory ConnectionFactory 19 | { 20 | get 21 | { 22 | return _connectionFactory; 23 | } 24 | 25 | set 26 | { 27 | if (value == null) 28 | throw new ArgumentNullException("value"); 29 | 30 | _connectionFactory = value; 31 | } 32 | } 33 | 34 | protected GearmanConnectionManager() 35 | { 36 | _connections = new List(); 37 | _connectionFactory = new GearmanConnectionFactory(); 38 | } 39 | 40 | protected GearmanConnectionManager(string clusterName) 41 | : this() 42 | { 43 | if (clusterName == null) 44 | throw new ArgumentNullException("clusterName"); 45 | 46 | var section = ConfigurationManager.GetSection("gearman") as GearmanConfigurationSection; 47 | if (section == null) 48 | throw new ConfigurationErrorsException("Section gearman is not found."); 49 | 50 | ParseConfiguration(section.Clusters[clusterName]); 51 | } 52 | 53 | protected GearmanConnectionManager(ClusterConfigurationElement clusterConfiguration) 54 | : this() 55 | { 56 | if (clusterConfiguration == null) 57 | throw new ArgumentNullException("clusterConfiguration"); 58 | 59 | ParseConfiguration(clusterConfiguration); 60 | } 61 | 62 | public void Dispose() 63 | { 64 | // http://codecrafter.blogspot.se/2010/01/better-idisposable-pattern.html 65 | CleanUpManagedResources(); 66 | GC.SuppressFinalize(this); 67 | } 68 | 69 | protected virtual void CleanUpManagedResources() 70 | { 71 | DisconnectAll(); 72 | } 73 | 74 | private void ParseConfiguration(ClusterConfigurationElement cluster) 75 | { 76 | foreach (ServerConfigurationElement server in cluster.Servers) 77 | { 78 | AddServer(server.Host, server.Port); 79 | } 80 | } 81 | 82 | public void AddServer(string host) 83 | { 84 | AddServer(host, _DEFAULT_PORT); 85 | } 86 | 87 | public void AddServer(string host, int port) 88 | { 89 | AddConnection(ConnectionFactory.CreateConnection(host, port)); 90 | } 91 | 92 | public void DisconnectAll() 93 | { 94 | foreach (var connection in _connections) 95 | { 96 | if (connection != null) 97 | { 98 | connection.Disconnect(); 99 | } 100 | } 101 | } 102 | 103 | private void AddConnection(IGearmanConnection connection) 104 | { 105 | if (connection == null) 106 | throw new ArgumentNullException("connection"); 107 | 108 | _connections.Add(connection); 109 | } 110 | 111 | protected IEnumerable GetAliveConnections() 112 | { 113 | var connections = _connections.Shuffle(new Random()).ToList(); 114 | var isAllDead = _connections.Where(conn => conn.IsDead()).Count() == _connections.Count; 115 | var aliveConnections = new List(); 116 | 117 | foreach (var connection in connections) 118 | { 119 | // Try to reconnect if they're not connected and not dead, or if all servers are dead, we will try to reconnect them anyway. 120 | if (!connection.IsConnected() && (!connection.IsDead() || isAllDead)) 121 | { 122 | try 123 | { 124 | connection.Connect(); 125 | OnConnectionConnected(connection); 126 | 127 | // quick idea: Make GearmanConnection a base class and sub class it differently for the 128 | // client and the worker, where the worker always registers all functions when connecting? 129 | // Could that work? 130 | } 131 | catch (GearmanConnectionException) 132 | { 133 | // Is it enough to catch GearmanConnectionException? 134 | connection.MarkAsDead(); 135 | continue; 136 | } 137 | } 138 | 139 | if (connection.IsConnected()) 140 | { 141 | aliveConnections.Add(connection); 142 | } 143 | } 144 | 145 | return aliveConnections; 146 | } 147 | 148 | protected virtual void OnConnectionConnected(IGearmanConnection connection) 149 | { 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanJob.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman 5 | { 6 | public delegate void GearmanJobFunction(IGearmanJob job) 7 | where TArg : class 8 | where TResult : class; 9 | 10 | public enum GearmanJobPriority 11 | { 12 | High = 1, 13 | Normal = 2, 14 | Low = 3 15 | }; 16 | 17 | public class GearmanJob : IGearmanJob 18 | where TArg : class 19 | where TResult : class 20 | { 21 | private readonly DataSerializer _serializer; 22 | private readonly DataDeserializer _deserializer; 23 | private readonly GearmanWorkerProtocol _protocol; 24 | 25 | public GearmanJobInfo Info { get; protected set; } 26 | public TArg FunctionArgument { get; protected set; } 27 | 28 | public GearmanJob(GearmanWorkerProtocol protocol, GearmanJobInfo jobAssignment, 29 | DataDeserializer argumentDeserializer, DataSerializer resultSerializer) 30 | { 31 | _serializer = resultSerializer; 32 | _deserializer = argumentDeserializer; 33 | _protocol = protocol; 34 | Info = jobAssignment; 35 | FunctionArgument = _deserializer(jobAssignment.FunctionArgument); 36 | } 37 | 38 | public void Complete() 39 | { 40 | _protocol.WorkComplete(Info.JobHandle); 41 | } 42 | 43 | public void Complete(TResult result) 44 | { 45 | _protocol.WorkComplete(Info.JobHandle, _serializer(result)); 46 | } 47 | 48 | public void Fail() 49 | { 50 | _protocol.WorkFail(Info.JobHandle); 51 | } 52 | 53 | public void SetStatus(uint numerator, uint denominator) 54 | { 55 | _protocol.WorkStatus(Info.JobHandle, numerator, denominator); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanJobInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman 5 | { 6 | public class GearmanJobInfo 7 | { 8 | public string JobHandle { get; set; } 9 | public string FunctionName { get; set; } 10 | public byte[] FunctionArgument { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanJobPriority.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman 5 | { 6 | public enum GearmanJobPriority 7 | { 8 | High = 1, 9 | Normal = 2, 10 | Low = 3 11 | }; 12 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanJobRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace Twingly.Gearman 6 | { 7 | public class GearmanJobRequest 8 | { 9 | public IGearmanConnection Connection { get; protected set; } // could this be an IGearmanConnection instead? 10 | 11 | public string JobHandle { get; set; } 12 | 13 | public GearmanJobRequest(IGearmanConnection connection, string jobHandle) 14 | { 15 | if (connection == null) 16 | throw new ArgumentNullException("connection"); 17 | 18 | if (jobHandle == null) 19 | throw new ArgumentNullException("jobHandle"); 20 | 21 | Connection = connection; 22 | JobHandle = jobHandle; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanJobStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman 5 | { 6 | public class GearmanJobStatus 7 | { 8 | public string JobHandle { get; protected set; } 9 | public bool IsKnown { get; protected set; } 10 | public bool IsRunning { get; protected set; } 11 | public uint CompletionNumerator { get; protected set; } 12 | public uint CompletionDenominator { get; protected set; } 13 | 14 | public GearmanJobStatus(string jobHandle, bool isKnown, bool isRunning, uint completionNumerator, uint completionDenominator) 15 | { 16 | JobHandle = jobHandle; 17 | IsKnown = isKnown; 18 | IsRunning = isRunning; 19 | CompletionNumerator = completionNumerator; 20 | CompletionDenominator = completionDenominator; 21 | } 22 | 23 | public double GetCompletionPercent() 24 | { 25 | return CompletionNumerator / (double)CompletionDenominator; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | using Twingly.Gearman.Exceptions; 6 | using Twingly.Gearman.Packets; 7 | 8 | namespace Twingly.Gearman 9 | { 10 | public abstract class GearmanProtocol 11 | { 12 | public IGearmanConnection Connection { get; protected set; } 13 | 14 | protected GearmanProtocol(IGearmanConnection connection) 15 | { 16 | Connection = connection; 17 | } 18 | 19 | public static GearmanServerException UnpackErrorReponse(IResponsePacket response) 20 | { 21 | var args = Util.SplitArray(response.GetData()); 22 | throw new GearmanServerException(Encoding.UTF8.GetString(args[0]), Encoding.UTF8.GetString(args[1])); 23 | } 24 | 25 | public static RequestPacket PackRequest(PacketType packetType) 26 | { 27 | return new RequestPacket(packetType); 28 | } 29 | 30 | public static RequestPacket PackRequest(PacketType packetType, string arg1) 31 | { 32 | if (arg1 == null) 33 | throw new ArgumentNullException("arg1"); 34 | 35 | return new RequestPacket(packetType, Encoding.UTF8.GetBytes(arg1)); 36 | } 37 | 38 | public static RequestPacket PackRequest(PacketType packetType, string arg1, byte[] arg2) 39 | { 40 | if (arg1 == null) 41 | throw new ArgumentNullException("arg1"); 42 | 43 | if (arg2 == null) 44 | throw new ArgumentNullException("arg2"); 45 | 46 | return new RequestPacket(packetType, JoinByteArraysForData(Encoding.UTF8.GetBytes(arg1), arg2)); 47 | } 48 | 49 | public static RequestPacket PackRequest(PacketType packetType, string arg1, string arg2) 50 | { 51 | if (arg1 == null) 52 | throw new ArgumentNullException("arg1"); 53 | 54 | if (arg2 == null) 55 | throw new ArgumentNullException("arg2"); 56 | 57 | return new RequestPacket(packetType, JoinByteArraysForData(Encoding.UTF8.GetBytes(arg1), Encoding.UTF8.GetBytes(arg1))); 58 | } 59 | 60 | public static RequestPacket PackRequest(PacketType packetType, string arg1, string arg2, string arg3) 61 | { 62 | if (arg1 == null) 63 | throw new ArgumentNullException("arg1"); 64 | 65 | if (arg2 == null) 66 | throw new ArgumentNullException("arg2"); 67 | 68 | if (arg3 == null) 69 | throw new ArgumentNullException("arg3"); 70 | 71 | return new RequestPacket(packetType, 72 | JoinByteArraysForData(Encoding.UTF8.GetBytes(arg1), Encoding.UTF8.GetBytes(arg2), Encoding.UTF8.GetBytes(arg3))); 73 | } 74 | 75 | public static RequestPacket PackRequest(PacketType packetType, string arg1, string arg2, byte[] arg3) 76 | { 77 | if (arg1 == null) 78 | throw new ArgumentNullException("arg1"); 79 | 80 | if (arg2 == null) 81 | throw new ArgumentNullException("arg2"); 82 | 83 | if (arg3 == null) 84 | throw new ArgumentNullException("arg3"); 85 | 86 | return new RequestPacket(packetType, 87 | JoinByteArraysForData(Encoding.UTF8.GetBytes(arg1), Encoding.UTF8.GetBytes(arg2), arg3)); 88 | } 89 | 90 | public static string UnpackJobCreatedResponse(IResponsePacket response) 91 | { 92 | return Encoding.UTF8.GetString(response.GetData()); 93 | } 94 | 95 | public static GearmanJobInfo UnpackJobAssignResponse(IResponsePacket response) 96 | { 97 | var args = Util.SplitArray(response.GetData()); 98 | return new GearmanJobInfo 99 | { 100 | JobHandle = Encoding.UTF8.GetString(args[0]), 101 | FunctionName = Encoding.UTF8.GetString(args[1]), 102 | FunctionArgument = args[2] 103 | }; 104 | } 105 | 106 | public static GearmanJobStatus UnpackStatusResponse(IResponsePacket response) 107 | { 108 | var args = Util.SplitArray(response.GetData()); 109 | return new GearmanJobStatus( 110 | Encoding.UTF8.GetString(args[0]), 111 | uint.Parse(Encoding.UTF8.GetString(args[1])) == 0 ? false : true, 112 | uint.Parse(Encoding.UTF8.GetString(args[2])) == 0 ? false : true, 113 | uint.Parse(Encoding.UTF8.GetString(args[3])), 114 | uint.Parse(Encoding.UTF8.GetString(args[4]))); 115 | } 116 | 117 | public static GearmanJobData UnpackWorkDataResponse(IResponsePacket response) 118 | { 119 | var args = Util.SplitArray(response.GetData()); 120 | return new GearmanJobData(Encoding.UTF8.GetString(args[0]), args[1]); 121 | } 122 | 123 | public static GearmanJobData UnpackWorkCompleteResponse(IResponsePacket response) 124 | { 125 | return UnpackWorkDataResponse(response); 126 | } 127 | 128 | /// 129 | /// Concatenates a number of byte arrays with \0 between them. 130 | /// 131 | public static byte[] JoinByteArraysForData(params byte[][] data) 132 | { 133 | const byte splitByte = 0; 134 | 135 | int len = (data.Length == 0 ? 0 : data.Length - 1); 136 | foreach (var arr in data) 137 | { 138 | len += arr.Length; 139 | } 140 | 141 | var result = new byte[len]; 142 | var offset = 0; 143 | bool first = true; 144 | foreach (var arr in data) 145 | { 146 | // Add \0 before all values, except for the first. (i.e. append it for all but the last) 147 | if (first) 148 | first = false; 149 | else 150 | result[offset++] = splitByte; 151 | Array.Copy(arr, 0, result, offset, arr.Length); 152 | offset += arr.Length; 153 | } 154 | 155 | return result; 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {8B999000-26B7-4E65-9361-92785B249350} 9 | Library 10 | Properties 11 | Twingly.Gearman 12 | Twingly.Gearman 13 | v3.5 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | ..\lib\Newtonsoft.Json.dll 37 | 38 | 39 | 40 | 41 | 3.5 42 | 43 | 44 | 3.5 45 | 46 | 47 | 3.5 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 104 | -------------------------------------------------------------------------------- /GearmanSharp/GearmanThreadedWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using Twingly.Gearman.Configuration; 6 | 7 | namespace Twingly.Gearman 8 | { 9 | public class GearmanThreadedWorker : GearmanWorker 10 | { 11 | private const int _NO_JOB_COUNT_BEFORE_SLEEP = 10; 12 | private const int _NO_JOB_SLEEP_TIME_MS = 1000; 13 | private const int _NO_SERVERS_SLEEP_TIME_MS = 1000; 14 | 15 | protected volatile bool ContinueWorking = false; 16 | private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); 17 | private readonly Thread _workLoopThread; 18 | 19 | public GearmanThreadedWorker() 20 | { 21 | _workLoopThread = new Thread(WorkLoopThreadProc); 22 | } 23 | 24 | public GearmanThreadedWorker(string clusterName) 25 | : base(clusterName) 26 | { 27 | _workLoopThread = new Thread(WorkLoopThreadProc); 28 | } 29 | 30 | public GearmanThreadedWorker(ClusterConfigurationElement clusterConfiguration) 31 | : base(clusterConfiguration) 32 | { 33 | _workLoopThread = new Thread(WorkLoopThreadProc); 34 | } 35 | 36 | public void StartWorkLoop() 37 | { 38 | ContinueWorking = true; 39 | _resetEvent.Reset(); 40 | _workLoopThread.Start(); 41 | } 42 | 43 | /// 44 | /// Tells the worker thread to stop and then joins the thread. 45 | /// 46 | public void StopWorkLoop() 47 | { 48 | SignalWorkerThreadToStop(); 49 | JoinWorkerThread(); 50 | } 51 | 52 | /// 53 | /// Tells the worker thread to stop and then joins the thread. 54 | /// 55 | public void SignalWorkerThreadToStop() 56 | { 57 | ContinueWorking = false; 58 | _resetEvent.Set(); 59 | } 60 | 61 | /// 62 | /// Joins the worker thread, if it's alive. 63 | /// 64 | public void JoinWorkerThread() 65 | { 66 | if (_workLoopThread.IsAlive) 67 | { 68 | _workLoopThread.Join(); 69 | } 70 | } 71 | 72 | /// 73 | /// Called when a job function throws an exception. Does nothing and returns false, to not abort the work loop. 74 | /// 75 | /// The exception thrown by the job function. 76 | /// The job assignment that the job function got. 77 | /// Return true if it should throw, or false if it should not throw after the return. 78 | protected override bool OnJobException(Exception exception, GearmanJobInfo jobAssignment) 79 | { 80 | // Don't throw the exception, as that would abort the work loop. 81 | return false; 82 | } 83 | 84 | private void WorkLoopThreadProc() 85 | { 86 | var noJobCount = 0; 87 | while (ContinueWorking) 88 | { 89 | try 90 | { 91 | var aliveConnections = GetAliveConnections(); 92 | 93 | if (aliveConnections.Count() < 1) 94 | { 95 | // No servers available, sleep for a while and try again later 96 | _resetEvent.WaitOne(_NO_SERVERS_SLEEP_TIME_MS, false); 97 | _resetEvent.Reset(); 98 | noJobCount = 0; 99 | } 100 | else 101 | { 102 | foreach (var connection in aliveConnections) 103 | { 104 | if (!ContinueWorking) 105 | { 106 | break; 107 | } 108 | 109 | var didWork = Work(connection); 110 | noJobCount = didWork ? 0 : noJobCount + 1; 111 | } 112 | 113 | if (noJobCount >= _NO_JOB_COUNT_BEFORE_SLEEP) 114 | { 115 | _resetEvent.WaitOne(_NO_JOB_SLEEP_TIME_MS, false); 116 | _resetEvent.Reset(); 117 | noJobCount = 0; 118 | } 119 | } 120 | } 121 | catch (Exception) 122 | { 123 | // TODO: Logging framework? 124 | ContinueWorking = false; 125 | } 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Twingly.Gearman.Configuration; 7 | using Twingly.Gearman.Exceptions; 8 | 9 | namespace Twingly.Gearman 10 | { 11 | public class GearmanWorker : GearmanConnectionManager 12 | { 13 | protected struct FunctionInformation 14 | { 15 | public Delegate ResultSerializer { get; set; } 16 | public Delegate ArgumentDeserializer { get; set; } 17 | public Delegate Function { get; set; } 18 | public Type ArgumentType { get; set; } 19 | public Type ResultType { get; set; } 20 | public ConstructorInfo JobConstructor { get; set; } 21 | } 22 | 23 | private string _clientId = null; 24 | private readonly IDictionary _functionInformation = new Dictionary(); 25 | 26 | public GearmanWorker() 27 | { 28 | } 29 | 30 | public GearmanWorker(string clusterName) 31 | : base(clusterName) 32 | { 33 | } 34 | 35 | public GearmanWorker(ClusterConfigurationElement clusterConfiguration) 36 | : base(clusterConfiguration) 37 | { 38 | } 39 | 40 | public void SetClientId(string clientId) 41 | { 42 | if (clientId == null) 43 | throw new ArgumentNullException("clientId"); 44 | 45 | _clientId = clientId; 46 | foreach (var connection in GetAliveConnections()) 47 | { 48 | SetClientId(connection); 49 | } 50 | } 51 | 52 | public void RegisterFunction(string functionName, GearmanJobFunction function) 53 | { 54 | RegisterFunction(functionName, function, data => (data), data => (data)); 55 | } 56 | 57 | public void RegisterFunction(string functionName, GearmanJobFunction function, 58 | DataDeserializer argumentDeserializer, DataSerializer resultSerializer) 59 | where TArg : class 60 | where TResult : class 61 | { 62 | if (functionName == null) 63 | throw new ArgumentNullException("functionName"); 64 | 65 | if (function == null) 66 | throw new ArgumentNullException("function"); 67 | 68 | if (resultSerializer == null) 69 | throw new ArgumentNullException("resultSerializer"); 70 | 71 | if (argumentDeserializer == null) 72 | throw new ArgumentNullException("argumentDeserializer"); 73 | 74 | AddFunction(functionName, function, resultSerializer, argumentDeserializer); 75 | 76 | foreach (var connection in GetAliveConnections()) 77 | { 78 | RegisterFunction(connection, functionName); 79 | } 80 | } 81 | 82 | public bool Work() 83 | { 84 | var aliveConnections = GetAliveConnections(); 85 | 86 | if (aliveConnections.Count() > 0) 87 | { 88 | return Work(aliveConnections.First()); 89 | } 90 | 91 | // What do we do if there are no alive servers? 92 | // When we call this function we only want to do one job and then it's interesting to know 93 | // if we didn't do any work because there weren't any, or because we didn't have any connections. 94 | throw new NoServerAvailableException("No job servers"); 95 | } 96 | 97 | protected bool Work(IGearmanConnection connection) 98 | { 99 | try 100 | { 101 | var protocol = new GearmanWorkerProtocol(connection); 102 | var jobAssignment = protocol.GrabJob(); 103 | 104 | if (jobAssignment == null) 105 | return false; 106 | 107 | if (!_functionInformation.ContainsKey(jobAssignment.FunctionName)) 108 | throw new GearmanApiException(String.Format("Received work for unknown function {0}", jobAssignment.FunctionName)); 109 | 110 | CallFunction(protocol, jobAssignment); 111 | return true; 112 | } 113 | catch (GearmanConnectionException) 114 | { 115 | connection.MarkAsDead(); 116 | return false; 117 | } 118 | catch (GearmanFunctionInternalException functionException) 119 | { 120 | // The job function threw an exception. Just as with other exceptions, we disconnect 121 | // from the server because we don't want the job to be removed. See general exception 122 | // catch for more information. 123 | connection.Disconnect(); 124 | var shouldThrow = OnJobException(functionException.InnerException, functionException.JobInfo); 125 | if (shouldThrow) 126 | { 127 | throw; 128 | } 129 | return false; 130 | } 131 | catch (Exception) 132 | { 133 | // We failed to call the function and there isn't any good response to send the server. 134 | // According to this response on the mailing list, the best action is probably to close the connection: 135 | // "A worker disconnect with no response message is currently how the server's retry behavior is triggered." 136 | // http://groups.google.com/group/gearman/browse_thread/thread/5c91acc31bd10688/529e586405ed37fe 137 | // 138 | // We can't send Complete or Fail for the job, because that would cause the job to be "done" and the server wouldn't retry. 139 | connection.Disconnect(); 140 | throw; 141 | } 142 | } 143 | 144 | protected override void OnConnectionConnected(IGearmanConnection connection) 145 | { 146 | RegisterAllFunctions(connection); 147 | SetClientId(connection); 148 | } 149 | 150 | /// 151 | /// Called when a job function throws an exception. The default implementation returns true. 152 | /// 153 | /// The exception thrown by the job function. 154 | /// The job assignment that the job function got. 155 | /// Return true if it should throw, or false if it should not throw after the return. 156 | protected virtual bool OnJobException(Exception exception, GearmanJobInfo jobAssignment) 157 | { 158 | return true; 159 | } 160 | 161 | private void SetClientId(IGearmanConnection connection) 162 | { 163 | try 164 | { 165 | new GearmanWorkerProtocol(connection).SetClientId(_clientId); 166 | } 167 | catch (GearmanConnectionException) 168 | { 169 | connection.MarkAsDead(); 170 | } 171 | } 172 | 173 | private void RegisterAllFunctions(IGearmanConnection connection) 174 | { 175 | foreach (var functionName in _functionInformation.Keys) 176 | { 177 | RegisterFunction(connection, functionName); 178 | } 179 | } 180 | 181 | private static void RegisterFunction(IGearmanConnection connection, string functionName) 182 | { 183 | try 184 | { 185 | new GearmanWorkerProtocol(connection).CanDo(functionName); 186 | } 187 | catch (GearmanConnectionException) 188 | { 189 | connection.MarkAsDead(); 190 | } 191 | } 192 | 193 | private void AddFunction(string functionName, GearmanJobFunction function, 194 | DataSerializer resultSerializer, DataDeserializer argumentDeserializer) 195 | where TArg : class 196 | where TResult : class 197 | { 198 | var jobConstructorTypes = new Type[4] 199 | { 200 | typeof(GearmanWorkerProtocol), 201 | typeof(GearmanJobInfo), 202 | typeof(DataDeserializer), 203 | typeof(DataSerializer) 204 | }; 205 | 206 | var jobConstructorInfo = typeof(GearmanJob).GetConstructor(jobConstructorTypes); 207 | 208 | if (jobConstructorInfo == null) 209 | throw new InvalidOperationException("Failed to locate the constructor for GearmanJob2"); 210 | 211 | _functionInformation.Add(functionName, new FunctionInformation 212 | { 213 | Function = function, 214 | ArgumentType = typeof(TArg), 215 | ResultType = typeof(TResult), 216 | ArgumentDeserializer = argumentDeserializer, 217 | ResultSerializer = resultSerializer, 218 | JobConstructor = jobConstructorInfo 219 | }); 220 | } 221 | 222 | private void CallFunction(GearmanWorkerProtocol protocol, GearmanJobInfo jobAssignment) 223 | { 224 | var functionInformation = _functionInformation[jobAssignment.FunctionName]; 225 | 226 | object job; 227 | 228 | try 229 | { 230 | job = functionInformation.JobConstructor.Invoke(new object[] 231 | { 232 | protocol, 233 | jobAssignment, 234 | functionInformation.ArgumentDeserializer, 235 | functionInformation.ResultSerializer, 236 | 237 | }); 238 | } 239 | catch (Exception ex) 240 | { 241 | throw new GearmanException("Failed to invoke the GearmanJob constructor", ex); 242 | } 243 | 244 | try 245 | { 246 | functionInformation.Function.DynamicInvoke(job); 247 | } 248 | catch (TargetInvocationException ex) 249 | { 250 | if (ex.InnerException != null) 251 | { 252 | // Remove the TargetInvocationException wrapper that DynamicInvoke added, 253 | // so we can give the user the exception from the job function. 254 | throw new GearmanFunctionInternalException( 255 | jobAssignment, 256 | String.Format("Function '{0}' threw exception", jobAssignment.FunctionName), ex.InnerException); 257 | } 258 | 259 | // If there is no inner exception, something strange is up, so then we want to throw this exception. 260 | throw new GearmanException("Failed to invoke the function dynamically", ex); 261 | } 262 | catch (Exception ex) 263 | { 264 | throw new GearmanException("Failed to invoke the function dynamically", ex); 265 | } 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /GearmanSharp/GearmanWorkerProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | using Twingly.Gearman.Exceptions; 6 | using Twingly.Gearman.Packets; 7 | 8 | namespace Twingly.Gearman 9 | { 10 | public class GearmanWorkerProtocol : GearmanProtocol 11 | { 12 | public GearmanWorkerProtocol(IGearmanConnection connection) 13 | : base(connection) 14 | { 15 | } 16 | 17 | public void SetClientId(string clientId) 18 | { 19 | Connection.SendPacket(PackRequest(PacketType.SET_CLIENT_ID, clientId)); 20 | } 21 | 22 | public void CanDo(string functionName) 23 | { 24 | Connection.SendPacket(PackRequest(PacketType.CAN_DO, functionName)); 25 | } 26 | 27 | public GearmanJobInfo GrabJob() 28 | { 29 | Connection.SendPacket(PackRequest(PacketType.GRAB_JOB)); 30 | 31 | IResponsePacket response; 32 | do 33 | { 34 | response = Connection.GetNextPacket(); // Throw away all NOOPs. 35 | } while (response.Type == PacketType.NOOP); 36 | 37 | if (response.Type == PacketType.ERROR) 38 | { 39 | throw UnpackErrorReponse(response); 40 | } 41 | 42 | GearmanJobInfo job; 43 | if (response.Type == PacketType.JOB_ASSIGN) 44 | { 45 | job = UnpackJobAssignResponse(response); 46 | } 47 | else if (response.Type == PacketType.NO_JOB) 48 | { 49 | job = null; 50 | } 51 | else 52 | { 53 | throw new GearmanApiException("Got unknown packet from server"); 54 | } 55 | 56 | return job; 57 | } 58 | 59 | public void WorkComplete(string jobHandle) 60 | { 61 | WorkComplete(jobHandle, null); 62 | } 63 | 64 | public void WorkComplete(string jobHandle, byte[] result) 65 | { 66 | Connection.SendPacket(PackRequest(PacketType.WORK_COMPLETE, jobHandle, result ?? new byte[0])); 67 | } 68 | 69 | public void WorkFail(string jobHandle) 70 | { 71 | Connection.SendPacket(PackRequest(PacketType.WORK_FAIL, jobHandle)); 72 | } 73 | 74 | public void WorkStatus(string jobHandle, uint numerator, uint denominator) 75 | { 76 | // The numerator and denominator should be sent as text, not binary. 77 | Connection.SendPacket(PackRequest(PacketType.WORK_STATUS, jobHandle, numerator.ToString(), denominator.ToString())); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /GearmanSharp/IGearmanClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Twingly.Gearman 4 | { 5 | public interface IGearmanClient : IDisposable 6 | { 7 | GearmanJobStatus GetStatus(GearmanJobRequest jobRequest); 8 | 9 | byte[] SubmitJob(string functionName, byte[] functionArgument); 10 | byte[] SubmitJob(string functionName, byte[] functionArgument, string uniqueId, GearmanJobPriority priority); 11 | 12 | TResult SubmitJob(string functionName, TArg functionArgument, 13 | DataSerializer argumentSerializer, DataDeserializer resultDeserializer) 14 | where TArg : class 15 | where TResult : class; 16 | 17 | TResult SubmitJob(string functionName, TArg functionArgument, string uniqueId, GearmanJobPriority priority, 18 | DataSerializer argumentSerializer, DataDeserializer resultDeserializer) 19 | where TArg : class 20 | where TResult : class; 21 | 22 | 23 | GearmanJobRequest SubmitBackgroundJob(string functionName, byte[] functionArgument); 24 | GearmanJobRequest SubmitBackgroundJob(string functionName, byte[] functionArgument, string uniqueId, GearmanJobPriority priority); 25 | 26 | GearmanJobRequest SubmitBackgroundJob(string functionName, TArg functionArgument, 27 | DataSerializer argumentSerializer) 28 | where TArg : class; 29 | 30 | GearmanJobRequest SubmitBackgroundJob(string functionName, TArg functionArgument, string uniqueId, GearmanJobPriority priority, 31 | DataSerializer argumentSerializer) 32 | where TArg : class; 33 | } 34 | } -------------------------------------------------------------------------------- /GearmanSharp/IGearmanConnection.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | using Twingly.Gearman.Packets; 4 | 5 | namespace Twingly.Gearman 6 | { 7 | public interface IGearmanConnection 8 | { 9 | void Connect(); 10 | void Disconnect(); 11 | void SendPacket(RequestPacket p); 12 | IResponsePacket GetNextPacket(); 13 | 14 | bool IsConnected(); 15 | 16 | string Host { get; } 17 | int Port { get; } 18 | 19 | bool IsDead(); // A dead connection should not be retried. When it's time to retry, it won't be dead. 20 | void MarkAsDead(); 21 | } 22 | } -------------------------------------------------------------------------------- /GearmanSharp/IGearmanConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman 5 | { 6 | public interface IGearmanConnectionFactory 7 | { 8 | IGearmanConnection CreateConnection(string host, int port); 9 | } 10 | } -------------------------------------------------------------------------------- /GearmanSharp/IGearmanJob.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman 5 | { 6 | public interface IGearmanJob 7 | { 8 | GearmanJobInfo Info { get; } 9 | 10 | /// 11 | /// The deserialized function argument. 12 | /// 13 | TArg FunctionArgument { get; } 14 | 15 | void Complete(); 16 | void Complete(TResult result); 17 | 18 | void Fail(); 19 | 20 | // Using GEARMAND_COMMAND_WORK_EXCEPTION is not recommended at time of this writing 21 | // http://groups.google.com/group/gearman/browse_thread/thread/5c91acc31bd10688/529e586405ed37fe 22 | // 23 | //void Exception(); 24 | //void Exception(byte[] exception); 25 | 26 | void SetStatus(uint numerator, uint denominator); 27 | } 28 | } -------------------------------------------------------------------------------- /GearmanSharp/ISocket.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | using System.Net.Sockets; 4 | 5 | namespace Twingly.Gearman 6 | { 7 | /// 8 | /// Represents an abstract Socket (that can be mocked). 9 | /// 10 | public interface ISocket 11 | { 12 | bool Connected { get; } 13 | 14 | void Connect(string host, int port); 15 | int Send(byte[] buffer); 16 | int Receive(byte[] buffer, int size, SocketFlags socketFlags); 17 | int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags); 18 | void Shutdown(); 19 | void Close(); 20 | } 21 | } -------------------------------------------------------------------------------- /GearmanSharp/Packets/Packet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace Twingly.Gearman.Packets 8 | { 9 | public abstract class Packet 10 | { 11 | private readonly byte[] _packetData; 12 | 13 | public PacketType Type { get; protected set; } 14 | 15 | protected Packet(PacketType packetType, byte[] packetData) 16 | { 17 | Type = packetType; 18 | _packetData = packetData; 19 | } 20 | 21 | public abstract byte[] GetMagic(); 22 | 23 | private byte[] GetHeader(int dataSize) 24 | { 25 | var header = new byte[12]; 26 | Array.Copy(GetMagic(), 0, header, 0, 4); 27 | Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)Type)), 0, header, 4, 4); 28 | Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(dataSize)), 0, header, 8, 4); 29 | return header; 30 | } 31 | 32 | public virtual byte[] GetData() 33 | { 34 | return _packetData; 35 | } 36 | 37 | public virtual byte[] ToByteArray() 38 | { 39 | var data = GetData(); 40 | var header = GetHeader(data.Length); 41 | var arr = new byte[header.Length + data.Length]; 42 | Array.Copy(header, 0, arr, 0, header.Length); 43 | Array.Copy(data, 0, arr, header.Length, data.Length); 44 | return arr; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /GearmanSharp/Packets/PacketType.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace Twingly.Gearman.Packets 5 | { 6 | public enum PacketType 7 | { 8 | // ReSharper disable InconsistentNaming 9 | 10 | // Name # Magic Type 11 | CAN_DO = 1, // REQ Worker 12 | CANT_DO = 2, // REQ Worker 13 | RESET_ABILITIES = 3, // REQ Worker 14 | PRE_SLEEP = 4, // REQ Worker 15 | // (unused) 5 - - 16 | NOOP = 6, // RES Worker 17 | SUBMIT_JOB = 7, // REQ Client 18 | JOB_CREATED = 8, // RES Client 19 | GRAB_JOB = 9, // REQ Worker 20 | NO_JOB = 10, // RES Worker 21 | JOB_ASSIGN = 11, // RES Worker 22 | WORK_STATUS = 12, // REQ Worker 23 | // RES Client 24 | WORK_COMPLETE = 13, // REQ Worker 25 | // RES Client 26 | WORK_FAIL = 14, // REQ Worker 27 | // RES Client 28 | GET_STATUS = 15, // REQ Client 29 | ECHO_REQ = 16, // REQ Client/Worker 30 | ECHO_RES = 17, // RES Client/Worker 31 | SUBMIT_JOB_BG = 18, // REQ Client 32 | ERROR = 19, // RES Client/Worker 33 | STATUS_RES = 20, // RES Client 34 | SUBMIT_JOB_HIGH = 21, // REQ Client 35 | SET_CLIENT_ID = 22, // REQ Worker 36 | CAN_DO_TIMEOUT = 23, // REQ Worker 37 | ALL_YOURS = 24, // REQ Worker 38 | WORK_EXCEPTION = 25, // REQ Worker 39 | // RES Client 40 | OPTION_REQ = 26, // REQ Client/Worker 41 | OPTION_RES = 27, // RES Client/Worker 42 | WORK_DATA = 28, // REQ Worker 43 | // RES Client 44 | WORK_WARNING = 29, // REQ Worker 45 | // RES Client 46 | GRAB_JOB_UNIQ = 30, // REQ Worker 47 | JOB_ASSIGN_UNIQ = 31, // RES Worker 48 | SUBMIT_JOB_HIGH_BG = 32, // REQ Client 49 | SUBMIT_JOB_LOW = 33, // REQ Client 50 | SUBMIT_JOB_LOW_BG = 34, // REQ Client 51 | SUBMIT_JOB_SCHED = 35, // REQ Client 52 | SUBMIT_JOB_EPOCH = 36, // REQ Client 53 | 54 | // ReSharper restore InconsistentNaming 55 | } 56 | } -------------------------------------------------------------------------------- /GearmanSharp/Packets/RequestPacket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace Twingly.Gearman.Packets 8 | { 9 | public class RequestPacket : Packet 10 | { 11 | public static readonly byte[] Magic = new byte[4] { 0, (byte)'R', (byte)'E', (byte)'Q' }; 12 | 13 | public RequestPacket(PacketType packetType) 14 | : this(packetType, new byte[0]) 15 | { 16 | } 17 | 18 | public RequestPacket(PacketType packetType, byte[] packetData) 19 | : base(packetType, packetData) 20 | { 21 | } 22 | 23 | public override byte[] GetMagic() 24 | { 25 | return Magic; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /GearmanSharp/Packets/ResponsePacket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | using Twingly.Gearman.Exceptions; 6 | 7 | namespace Twingly.Gearman.Packets 8 | { 9 | public interface IResponsePacket 10 | { 11 | byte[] GetMagic(); 12 | byte[] GetData(); 13 | PacketType Type { get; } 14 | byte[] ToByteArray(); 15 | } 16 | 17 | public class ResponsePacket : Packet, IResponsePacket 18 | { 19 | public static readonly byte[] Magic = new byte[4] { 0, (byte)'R', (byte)'E', (byte)'S' }; 20 | 21 | public ResponsePacket(PacketType packetType, byte[] packetData) 22 | : base(packetType, packetData) 23 | { 24 | } 25 | 26 | public override byte[] GetMagic() 27 | { 28 | return Magic; 29 | } 30 | 31 | public static int ParseString(byte[] data, int startIndex, out string str) 32 | { 33 | int offset = startIndex; 34 | for (; offset < data.Length && data[offset] != 0; offset++) { } 35 | str = Encoding.UTF8.GetString(data.Slice(startIndex, offset)); 36 | 37 | return offset + 1; // next position 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /GearmanSharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("GearmanSharp")] 9 | [assembly: AssemblyDescription("API for Gearman (http://www.gearman.org)")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Twingly AB")] 12 | [assembly: AssemblyProduct("GearmanSharp")] 13 | [assembly: AssemblyCopyright("Copyright © Twingly AB 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e0f91483-969e-42a7-87ba-6ee30a3eac4c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.3.3.0")] 36 | [assembly: AssemblyFileVersion("0.3.3.0")] 37 | -------------------------------------------------------------------------------- /GearmanSharp/Serializers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace Twingly.Gearman 8 | { 9 | public delegate byte[] DataSerializer(T data) where T : class; 10 | public delegate T DataDeserializer(byte[] data) where T : class; 11 | 12 | public static class Serializers 13 | { 14 | public static byte[] UTF8StringSerialize(string data) 15 | { 16 | if (data == null) 17 | return null; 18 | 19 | return Encoding.UTF8.GetBytes(data); 20 | } 21 | 22 | public static string UTF8StringDeserialize(byte[] data) 23 | { 24 | if (data == null) 25 | return null; 26 | 27 | return Encoding.UTF8.GetString(data); 28 | } 29 | 30 | public static byte[] JsonSerialize(T data) where T : class 31 | { 32 | if (data == null) 33 | return null; 34 | 35 | var jsonStr = JsonConvert.SerializeObject(data); 36 | return Encoding.UTF8.GetBytes(jsonStr); 37 | } 38 | 39 | public static T JsonDeserialize(byte[] data) where T : class 40 | { 41 | if (data == null) 42 | return null; 43 | 44 | var jsonStr = Encoding.UTF8.GetString(data); 45 | return JsonConvert.DeserializeObject(jsonStr); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GearmanSharp/SocketAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | 3 | namespace Twingly.Gearman 4 | { 5 | /// 6 | /// Adapter for System.Net.Sockets.Socket that implements ISocket. 7 | /// Passes all calls to the underlying Socket. 8 | /// AddressFamily.InterNetwork. SocketType.Stream. ProtocolType.Tcp. 9 | /// 10 | public class SocketAdapter : ISocket 11 | { 12 | private readonly Socket _socket; 13 | 14 | public SocketAdapter(Socket socket) 15 | { 16 | _socket = socket; 17 | } 18 | 19 | public virtual bool Connected 20 | { 21 | get { return _socket.Connected; } 22 | } 23 | 24 | public virtual void Connect(string host, int port) 25 | { 26 | _socket.Connect(host, port); 27 | } 28 | 29 | public virtual int Send(byte[] buffer) 30 | { 31 | return _socket.Send(buffer); 32 | } 33 | 34 | public virtual int Receive(byte[] buffer, int size, SocketFlags socketFlags) 35 | { 36 | return _socket.Receive(buffer, size, socketFlags); 37 | } 38 | 39 | public virtual int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags) 40 | { 41 | return _socket.Receive(buffer, offset, size, socketFlags); 42 | } 43 | 44 | public virtual void Shutdown() 45 | { 46 | _socket.Shutdown(SocketShutdown.Both); 47 | } 48 | 49 | public virtual void Close() 50 | { 51 | _socket.Close(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /GearmanSharp/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Twingly.Gearman 7 | { 8 | public static class Util 9 | { 10 | 11 | 12 | /// 13 | /// Splits a byte array on \0. Works as String.Split 14 | /// 15 | public static byte[][] SplitArray(byte[] arr) 16 | { 17 | const byte splitByte = 0; 18 | 19 | var segments = new List(); 20 | int lastPos = 0; 21 | while (true) 22 | { 23 | var pos = Array.IndexOf(arr, splitByte, lastPos); 24 | if (pos == -1) 25 | { 26 | pos = arr.Length; 27 | } 28 | 29 | var len = pos - lastPos; 30 | var segment = new byte[len]; 31 | Array.Copy(arr, lastPos, segment, 0, len); 32 | segments.Add(segment); 33 | 34 | if (pos < arr.Length) 35 | lastPos = pos + 1; // account for the byte we split on 36 | else 37 | break; 38 | } 39 | 40 | return segments.ToArray(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /GearmanSharp/docs/protocol.txt: -------------------------------------------------------------------------------- 1 | # Gearman server and library 2 | # Copyright (C) 2008 Brian Aker, Eric Day 3 | # All rights reserved. 4 | # 5 | # Use and distribution licensed under the BSD license. See 6 | # the COPYING file in this directory for full text. 7 | 8 | Gearman Protocol 9 | ---------------- 10 | 11 | The Gearman protocol operates over TCP, port 4730 by default. It 12 | previously operated on port 7003, but this conflicted with the AFS 13 | port range and the new port (4730) was assigned by IANA. Communication 14 | happens between either a client and job server, or between a worker 15 | and job server. In either case, the protocol consists of packets 16 | containing requests and responses. All packets sent to a job server 17 | are considered requests, and all packets sent from a job server are 18 | considered responses. A simple configuration may look like: 19 | 20 | ---------- ---------- ---------- ---------- 21 | | Client | | Client | | Client | | Client | 22 | ---------- ---------- ---------- ---------- 23 | \ / \ / 24 | \ / \ / 25 | -------------- -------------- 26 | | Job Server | | Job Server | 27 | -------------- -------------- 28 | | | 29 | ---------------------------------------------- 30 | | | | | 31 | ---------- ---------- ---------- ---------- 32 | | Worker | | Worker | | Worker | | Worker | 33 | ---------- ---------- ---------- ---------- 34 | 35 | Initially, the workers register functions they can perform with each 36 | job server. Clients will then connect to a job server and issue a 37 | request to a job to be run. The job server then notifies each worker 38 | that can perform that job (based on the function it registered) that 39 | a new job is ready. The first worker to wake up and retrieve the job 40 | will then execute it. 41 | 42 | All communication between workers or clients and the job server 43 | are binary. There is also a line-based text protocol used by 44 | administrative clients. This part of the protocol is text based so a 45 | custom administrative utility is not required (instead, 'telnet' or 46 | 'nc' can be used). This is documented under "Administrative Protocol". 47 | 48 | 49 | Binary Packet 50 | ------------- 51 | 52 | Requests and responses are encapsulated by a binary packet. A binary 53 | packet consists of a header which is optionally followed by data. The 54 | header is: 55 | 56 | 4 byte magic code - This is either "\0REQ" for requests or "\0RES" 57 | for responses. 58 | 59 | 4 byte type - A big-endian (network-order) integer containing 60 | an enumerated packet type. Possible values are: 61 | 62 | # Name Magic Type 63 | 1 CAN_DO REQ Worker 64 | 2 CANT_DO REQ Worker 65 | 3 RESET_ABILITIES REQ Worker 66 | 4 PRE_SLEEP REQ Worker 67 | 5 (unused) - - 68 | 6 NOOP RES Worker 69 | 7 SUBMIT_JOB REQ Client 70 | 8 JOB_CREATED RES Client 71 | 9 GRAB_JOB REQ Worker 72 | 10 NO_JOB RES Worker 73 | 11 JOB_ASSIGN RES Worker 74 | 12 WORK_STATUS REQ Worker 75 | RES Client 76 | 13 WORK_COMPLETE REQ Worker 77 | RES Client 78 | 14 WORK_FAIL REQ Worker 79 | RES Client 80 | 15 GET_STATUS REQ Client 81 | 16 ECHO_REQ REQ Client/Worker 82 | 17 ECHO_RES RES Client/Worker 83 | 18 SUBMIT_JOB_BG REQ Client 84 | 19 ERROR RES Client/Worker 85 | 20 STATUS_RES RES Client 86 | 21 SUBMIT_JOB_HIGH REQ Client 87 | 22 SET_CLIENT_ID REQ Worker 88 | 23 CAN_DO_TIMEOUT REQ Worker 89 | 24 ALL_YOURS REQ Worker 90 | 25 WORK_EXCEPTION REQ Worker 91 | RES Client 92 | 26 OPTION_REQ REQ Client/Worker 93 | 27 OPTION_RES RES Client/Worker 94 | 28 WORK_DATA REQ Worker 95 | RES Client 96 | 29 WORK_WARNING REQ Worker 97 | RES Client 98 | 30 GRAB_JOB_UNIQ REQ Worker 99 | 31 JOB_ASSIGN_UNIQ RES Worker 100 | 32 SUBMIT_JOB_HIGH_BG REQ Client 101 | 33 SUBMIT_JOB_LOW REQ Client 102 | 34 SUBMIT_JOB_LOW_BG REQ Client 103 | 35 SUBMIT_JOB_SCHED REQ Client 104 | 36 SUBMIT_JOB_EPOCH REQ Client 105 | 106 | 4 byte size - A big-endian (network-order) integer containing 107 | the size of the data being sent after the header. 108 | 109 | Arguments given in the data part are separated by a NULL byte, and 110 | the last argument is determined by the size of data after the last 111 | NULL byte separator. All job handle arguments must not be longer than 112 | 64 bytes, including NULL terminator. 113 | 114 | 115 | Client/Worker Requests 116 | ---------------------- 117 | 118 | These request types may be sent by either a client or a worker: 119 | 120 | ECHO_REQ 121 | 122 | When a job server receives this request, it simply generates a 123 | ECHO_RES packet with the data. This is primarily used for testing 124 | or debugging. 125 | 126 | Arguments: 127 | - Opaque data that is echoed back in response. 128 | 129 | 130 | Client/Worker Responses 131 | ----------------------- 132 | 133 | These response types may be sent to either a client or a worker: 134 | 135 | ECHO_RES 136 | 137 | This is sent in response to a ECHO_REQ request. The server doesn't 138 | look at or modify the data argument, it just sends it back. 139 | 140 | Arguments: 141 | - Opaque data that is echoed back in response. 142 | 143 | ERROR 144 | 145 | This is sent whenever the server encounters an error and needs 146 | to notify a client or worker. 147 | 148 | Arguments: 149 | - NULL byte terminated error code string. 150 | - Error text. 151 | 152 | 153 | Client Requests 154 | --------------- 155 | 156 | These request types may only be sent by a client: 157 | 158 | SUBMIT_JOB, SUBMIT_JOB_BG, 159 | SUBMIT_JOB_HIGH, SUBMIT_JOB_HIGH_BG, 160 | SUBMIT_JOB_LOW, SUBMIT_JOB_LOW_BG 161 | 162 | A client issues one of these when a job needs to be run. The 163 | server will then assign a job handle and respond with a JOB_CREATED 164 | packet. 165 | 166 | If on of the BG versions is used, the client is not updated with 167 | status or notified when the job has completed (it is detached). 168 | 169 | The Gearman job server queue is implemented with three levels: 170 | normal, high, and low. Jobs submitted with one of the HIGH versions 171 | always take precedence, and jobs submitted with the normal versions 172 | take precedence over the LOW versions. 173 | 174 | Arguments: 175 | - NULL byte terminated function name. 176 | - NULL byte terminated unique ID. 177 | - Opaque data that is given to the function as an argument. 178 | 179 | SUBMIT_JOB_SCHED 180 | 181 | Just like SUBMIT_JOB_BG, but run job at given time instead of 182 | immediately. This is not currently used and may be removed. 183 | 184 | Arguments: 185 | - NULL byte terminated function name. 186 | - NULL byte terminated unique ID. 187 | - NULL byte terminated minute (0-59). 188 | - NULL byte terminated hour (0-23). 189 | - NULL byte terminated day of month (1-31). 190 | - NULL byte terminated month (1-12). 191 | - NULL byte terminated day of week (0-6, 0 = Monday). 192 | - Opaque data that is given to the function as an argument. 193 | 194 | SUBMIT_JOB_EPOCH 195 | 196 | Just like SUBMIT_JOB_BG, but run job at given time instead of 197 | immediately. This is not currently used and may be removed. 198 | 199 | Arguments: 200 | - NULL byte terminated function name. 201 | - NULL byte terminated unique ID. 202 | - NULL byte terminated epoch time. 203 | - Opaque data that is given to the function as an argument. 204 | 205 | GET_STATUS 206 | 207 | A client issues this to get status information for a submitted job. 208 | 209 | Arguments: 210 | - Job handle that was given in JOB_CREATED packet. 211 | 212 | OPTION_REQ 213 | 214 | A client issues this to set an option for the connection in the 215 | job server. Returns a OPTION_RES packet on success, or an ERROR 216 | packet on failure. 217 | 218 | Arguments: 219 | - Name of the option to set. Possibilities are: 220 | * "exceptions" - Forward WORK_EXCEPTION packets to the client. 221 | 222 | 223 | Client Responses 224 | ---------------- 225 | 226 | These response types may only be sent to a client: 227 | 228 | JOB_CREATED 229 | 230 | This is sent in response to one of the SUBMIT_JOB* packets. It 231 | signifies to the client that a the server successfully received 232 | the job and queued it to be run by a worker. 233 | 234 | Arguments: 235 | - Job handle assigned by server. 236 | 237 | WORK_DATA, WORK_WARNING, WORK_STATUS, WORK_COMPLETE, 238 | WORK_FAIL, WORK_EXCEPTION 239 | 240 | For non-background jobs, the server forwards these packets from 241 | the worker to clients. See "Worker Requests" for more information 242 | and arguments. 243 | 244 | STATUS_RES 245 | 246 | This is sent in response to a GET_STATUS request. This is used by 247 | clients that have submitted a job with SUBMIT_JOB_BG to see if the 248 | job has been completed, and if not, to get the percentage complete. 249 | 250 | Arguments: 251 | - NULL byte terminated job handle. 252 | - NULL byte terminated known status, this is 0 (false) or 1 (true). 253 | - NULL byte terminated running status, this is 0 (false) or 1 254 | (true). 255 | - NULL byte terminated percent complete numerator. 256 | - Percent complete denominator. 257 | 258 | OPTION_RES 259 | 260 | Successful response to the OPTION_REQ request. 261 | 262 | Arguments: 263 | - Name of the option that was set, see OPTION_REQ for possibilities. 264 | 265 | 266 | Worker Requests 267 | --------------- 268 | 269 | These request types may only be sent by a worker: 270 | 271 | CAN_DO 272 | 273 | This is sent to notify the server that the worker is able to 274 | perform the given function. The worker is then put on a list to be 275 | woken up whenever the job server receives a job for that function. 276 | 277 | Arguments: 278 | - Function name. 279 | 280 | CAN_DO_TIMEOUT 281 | 282 | Same as CAN_DO, but with a timeout value on how long the job 283 | is allowed to run. After the timeout value, the job server will 284 | mark the job as failed and notify any listening clients. 285 | 286 | Arguments: 287 | - NULL byte terminated Function name. 288 | - Timeout value. 289 | 290 | CANT_DO 291 | 292 | This is sent to notify the server that the worker is no longer 293 | able to perform the given function. 294 | 295 | Arguments: 296 | - Function name. 297 | 298 | RESET_ABILITIES 299 | 300 | This is sent to notify the server that the worker is no longer 301 | able to do any functions it previously registered with CAN_DO or 302 | CAN_DO_TIMEOUT. 303 | 304 | Arguments: 305 | - None. 306 | 307 | PRE_SLEEP 308 | 309 | This is sent to notify the server that the worker is about to 310 | sleep, and that it should be woken up with a NOOP packet if a 311 | job comes in for a function the worker is able to perform. 312 | 313 | Arguments: 314 | - None. 315 | 316 | GRAB_JOB 317 | 318 | This is sent to the server to request any available jobs on the 319 | queue. The server will respond with either NO_JOB or JOB_ASSIGN, 320 | depending on whether a job is available. 321 | 322 | Arguments: 323 | - None. 324 | 325 | GRAB_JOB_UNIQ 326 | 327 | Just like GRAB_JOB, but return JOB_ASSIGN_UNIQ when there is a job. 328 | 329 | Arguments: 330 | - None. 331 | 332 | WORK_DATA 333 | 334 | This is sent to update the client with data from a running job. A 335 | worker should use this when it needs to send updates, send partial 336 | results, or flush data during long running jobs. It can also be 337 | used to break up a result so the worker does not need to buffer 338 | the entire result before sending in a WORK_COMPLETE packet. 339 | 340 | Arguments: 341 | - NULL byte terminated job handle. 342 | - Opaque data that is returned to the client. 343 | 344 | WORK_WARNING 345 | 346 | This is sent to update the client with a warning. It acts just 347 | like a WORK_DATA response, but should be treated as a warning 348 | instead of normal response data. 349 | 350 | Arguments: 351 | - NULL byte terminated job handle. 352 | - Opaque data that is returned to the client. 353 | 354 | WORK_STATUS 355 | 356 | This is sent to update the server (and any listening clients) 357 | of the status of a running job. The worker should send these 358 | periodically for long running jobs to update the percentage 359 | complete. The job server should store this information so a client 360 | who issued a background command may retrieve it later with a 361 | GET_STATUS request. 362 | 363 | Arguments: 364 | - NULL byte terminated job handle. 365 | - NULL byte terminated percent complete numerator. 366 | - Percent complete denominator. 367 | 368 | WORK_COMPLETE 369 | 370 | This is to notify the server (and any listening clients) that 371 | the job completed successfully. 372 | 373 | Arguments: 374 | - NULL byte terminated job handle. 375 | - Opaque data that is returned to the client as a response. 376 | 377 | WORK_FAIL 378 | 379 | This is to notify the server (and any listening clients) that 380 | the job failed. 381 | 382 | Arguments: 383 | - Job handle. 384 | 385 | WORK_EXCEPTION 386 | 387 | This is to notify the server (and any listening clients) that 388 | the job failed with the given exception. 389 | 390 | Arguments: 391 | - NULL byte terminated job handle. 392 | - Opaque data that is returned to the client as an exception. 393 | 394 | SET_CLIENT_ID 395 | 396 | This sets the worker ID in a job server so monitoring and reporting 397 | commands can uniquely identify the various workers, and different 398 | connections to job servers from the same worker. 399 | 400 | Arguments: 401 | - Unique string to identify the worker instance. 402 | 403 | ALL_YOURS 404 | 405 | Not yet implemented. This looks like it is used to notify a job 406 | server that this is the only job server it is connected to, so 407 | a job can be given directly to this worker with a JOB_ASSIGN and 408 | no worker wake-up is required. 409 | 410 | Arguments: 411 | - None. 412 | 413 | 414 | Worker Responses 415 | ---------------- 416 | 417 | These response types may only be sent to a worker: 418 | 419 | NOOP 420 | 421 | This is used to wake up a sleeping worker so that it may grab a 422 | pending job. 423 | 424 | Arguments: 425 | - None. 426 | 427 | NO_JOB 428 | 429 | This is given in response to a GRAB_JOB request to notify the 430 | worker there are no pending jobs that need to run. 431 | 432 | Arguments: 433 | - None. 434 | 435 | JOB_ASSIGN 436 | 437 | This is given in response to a GRAB_JOB request to give the worker 438 | information needed to run the job. All communication about the 439 | job (such as status updates and completion response) should use 440 | the handle, and the worker should run the given function with 441 | the argument. 442 | 443 | Arguments: 444 | - NULL byte terminated job handle. 445 | - NULL byte terminated function name. 446 | - Opaque data that is given to the function as an argument. 447 | 448 | JOB_ASSIGN_UNIQ 449 | 450 | This is given in response to a GRAB_JOB_UNIQ request and acts 451 | just like JOB_ASSIGN but with the client assigned unique ID. 452 | 453 | Arguments: 454 | - NULL byte terminated job handle. 455 | - NULL byte terminated function name. 456 | - NULL byte terminated unique ID. 457 | - Opaque data that is given to the function as an argument. 458 | 459 | 460 | Administrative Protocol 461 | ----------------------- 462 | 463 | The Gearman job server also supports a text-based protocol to pull 464 | information and run some administrative tasks. This runs on the same 465 | port as the binary protocol, and the server differentiates between 466 | the two by looking at the first character. If it is a NULL (\0), 467 | then it is binary, if it is non-NULL, that it attempts to parse it 468 | as a text command. The following commands are supported: 469 | 470 | workers 471 | 472 | This sends back a list of all workers, their file descriptors, 473 | their IPs, their IDs, and a list of registered functions they can 474 | perform. The list is terminated with a line containing a single 475 | '.' (period). The format is: 476 | 477 | FD IP-ADDRESS CLIENT-ID : FUNCTION ... 478 | 479 | Arguments: 480 | - None. 481 | 482 | status 483 | 484 | This sends back a list of all registered functions. Next to 485 | each function is the number of jobs in the queue, the number of 486 | running jobs, and the number of capable workers. The columns are 487 | tab separated, and the list is terminated with a line containing 488 | a single '.' (period). The format is: 489 | 490 | FUNCTION\tTOTAL\tRUNNING\tAVAILABLE_WORKERS 491 | 492 | Arguments: 493 | - None. 494 | 495 | maxqueue 496 | 497 | This sets the maximum queue size for a function. If no size is 498 | given, the default is used. If the size is negative, then the queue 499 | is set to be unlimited. This sends back a single line with "OK". 500 | 501 | Arguments: 502 | - Function name. 503 | - Optional maximum queue size. 504 | 505 | shutdown 506 | 507 | Shutdown the server. If the optional "graceful" argument is used, 508 | close the listening socket and let all existing connections 509 | complete. 510 | 511 | Arguments: 512 | - Optional "graceful" mode. 513 | 514 | version 515 | 516 | Send back the version of the server. 517 | 518 | Arguments: 519 | - None. 520 | 521 | 522 | The Perl version also has a 'gladiator' command that uses the 523 | 'Devel::Gladiator' Perl module and is used for debugging. 524 | 525 | 526 | Binary Protocol Example 527 | ----------------------- 528 | 529 | This example will step through a simple interaction where a worker 530 | connects and registers for a function named "reverse", the client 531 | connects and submits a job for this function, and the worker performs 532 | this job and responds with a result. This shows every byte that needs 533 | to be sent over the wire in order for the job to be run to completion. 534 | 535 | 536 | Worker registration: 537 | 538 | Worker -> Job Server 539 | 00 52 45 51 \0REQ (Magic) 540 | 00 00 00 01 1 (Packet type: CAN_DO) 541 | 00 00 00 07 7 (Packet length) 542 | 72 65 76 65 72 73 65 reverse (Function) 543 | 544 | 545 | Worker check for job: 546 | 547 | Worker -> Job Server 548 | 00 52 45 51 \0REQ (Magic) 549 | 00 00 00 09 9 (Packet type: GRAB_JOB) 550 | 00 00 00 00 0 (Packet length) 551 | 552 | Job Server -> Worker 553 | 00 52 45 53 \0RES (Magic) 554 | 00 00 00 0a 10 (Packet type: NO_JOB) 555 | 00 00 00 00 0 (Packet length) 556 | 557 | Worker -> Job Server 558 | 00 52 45 51 \0REQ (Magic) 559 | 00 00 00 04 4 (Packet type: PRE_SLEEP) 560 | 00 00 00 00 0 (Packet length) 561 | 562 | 563 | Client job submission: 564 | 565 | Client -> Job Server 566 | 00 52 45 51 \0REQ (Magic) 567 | 00 00 00 07 7 (Packet type: SUBMIT_JOB) 568 | 00 00 00 0d 13 (Packet length) 569 | 72 65 76 65 72 73 65 00 reverse\0 (Function) 570 | 00 \0 (Unique ID) 571 | 74 65 73 74 test (Workload) 572 | 573 | Job Server -> Client 574 | 00 52 45 53 \0RES (Magic) 575 | 00 00 00 08 8 (Packet type: JOB_CREATED) 576 | 00 00 00 07 7 (Packet length) 577 | 48 3a 6c 61 70 3a 31 H:lap:1 (Job handle) 578 | 579 | 580 | Worker wakeup: 581 | 582 | Job Server -> Worker 583 | 00 52 45 53 \0RES (Magic) 584 | 00 00 00 06 6 (Packet type: NOOP) 585 | 00 00 00 00 0 (Packet length) 586 | 587 | 588 | Worker check for job: 589 | 590 | Worker -> Job Server 591 | 00 52 45 51 \0REQ (Magic) 592 | 00 00 00 09 9 (Packet type: GRAB_JOB) 593 | 00 00 00 00 0 (Packet length) 594 | 595 | Job Server -> Worker 596 | 00 52 45 53 \0RES (Magic) 597 | 00 00 00 0b 11 (Packet type: JOB_ASSIGN) 598 | 00 00 00 14 20 (Packet length) 599 | 48 3a 6c 61 70 3a 31 00 H:lap:1\0 (Job handle) 600 | 72 65 76 65 72 73 65 00 reverse\0 (Function) 601 | 74 65 73 74 test (Workload) 602 | 603 | 604 | Worker response for job: 605 | 606 | Worker -> Job Server 607 | 00 52 45 51 \0REQ (Magic) 608 | 00 00 00 0d 13 (Packet type: WORK_COMPLETE) 609 | 00 00 00 0c 12 (Packet length) 610 | 48 3a 6c 61 70 3a 31 00 H:lap:1\0 (Job handle) 611 | 74 73 65 74 tset (Response) 612 | 613 | 614 | Job server response to client: 615 | 616 | Job Server -> Client 617 | 00 52 45 53 \0RES (Magic) 618 | 00 00 00 0d 13 (Packet type: WORK_COMPLETE) 619 | 00 00 00 0c 12 (Packet length) 620 | 48 3a 6c 61 70 3a 31 00 H:lap:1\0 (Job handle) 621 | 74 73 65 74 tset (Response) 622 | 623 | 624 | At this point, the worker would then ask for more jobs to run (the 625 | "Check for job" state above), and the client could submit more 626 | jobs. Note that the client is full duplex and could have multiple 627 | jobs being run over a single socket at the same time. The result 628 | packets may not be sent in the same order the jobs were submitted 629 | and instead interleaved with other job result packets. 630 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Twingly AB 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Twingly AB nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | GearmanSharp is a C# API for Gearman (http://www.gearman.org). 2 | 3 | 4 | Description 5 | =========== 6 | GearmanSharp is a C# API for Gearman. Currently it only provides the basic 7 | parts of the protocol, but there is enough to build a basic client and worker. 8 | In the future, we hope it will provide a more complete implementation of the 9 | protocol. It requires .NET 3.5. 10 | 11 | 12 | License 13 | ======= 14 | Copyright 2010 Twingly AB. GearmanSharp is provided under the three-clause 15 | BSD License. See the included LICENSE.txt file for specifics. 16 | 17 | 18 | Source code 19 | =========== 20 | The source code is located on GitHub at: 21 | http://github.com/twingly/GearmanSharp/ 22 | 23 | 24 | Examples 25 | ======== 26 | For more examples, check: 27 | http://github.com/twingly/GearmanSharp/blob/master/GearmanSharp/Examples/Example.cs 28 | 29 | 30 | * Client examples 31 | 32 | // Create a simple client and add "localhost" as server 33 | using (var client = new GearmanClient()) 34 | { 35 | client.AddServer("localhost"); 36 | 37 | // You can submit a simple background job 38 | client.SubmitBackgroundJob("reverse", 39 | Encoding.ASCII.GetBytes("helloworld")); 40 | 41 | // And you can submit a more advanced job 42 | var oembeds = client.SubmitJob, IList>( 43 | "GetOEmbeds", 44 | new List { "http://www.youtube.com/watch?v=abc123456" }, 45 | Serializers.JsonSerialize>, 46 | Serializers.JsonDeserialize>); 47 | } 48 | 49 | 50 | 51 | * Worker examples 52 | 53 | // Create a simple worker and register a function that handles "reverse" 54 | var worker = new GearmanWorker(); 55 | worker.AddServer("localhost"); 56 | worker.RegisterFunction("reverse", ReverseFunction); 57 | 58 | // Perform one unit of work 59 | worker.Work(); 60 | 61 | // You can also register more advanced functions 62 | worker.RegisterFunction, IList>("GetOEmbeds", 63 | GetOembedsFunction, 64 | Serializers.JsonDeserialize>, 65 | Serializers.JsonSerialize>); 66 | 67 | // The function definition and GearmanJob: 68 | public void GetOEmbeds(IGearmanJob, IList> job) 69 | { 70 | // The FunctionArgument of the job will be an IList 71 | IList urls = job.FunctionArgument; 72 | // ... 73 | 74 | // and the Complete(..) function takes an IList 75 | job.Complete(new List()); 76 | } 77 | 78 | // You can also start a worker in a separate thread 79 | var worker = new GearmanThreadedWorker(); 80 | 81 | // Start the worker thread 82 | worker.StartWorkLoop(); 83 | // ... do other stuff ... 84 | worker.StopWorkLoop(); 85 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | Gearman C# API TODO 2 | =================== 3 | * Logging. Preferably without relying on a single logging library such as log4net or nlog, since that should be the user's 4 | choice. 5 | 6 | * Add more tests. The reflection usage in GearmanWorker would be very good to test, since it's one of the more fragile parts 7 | (finding the constructor for GearmanJob for example). 8 | 9 | * Add support for retrying jobs. Not exactly sure how this is supposed to be handled with background jobs. 10 | http://search.cpan.org/~bradfitz/Gearman/lib/Gearman/Task.pm 11 | 12 | * Improve heuristic for sleeping when there are no jobs (in GearmanThreadedWorker). We could have some kind of increase 13 | in sleep time for each NO_JOB that we get, so we start with a few milliseconds and increase up-to say a second or two 14 | when we haven't gotten any jobs for a good while. 15 | 16 | An even better solution would be to implement support for PRE_SLEEP, but that's probably a lot more work. 17 | 18 | * Add option to specify ReceiveTimeout on GearmanConnection when submitting foreground jobs. It might be that it's not 19 | possible to handle timeouts well, because the socket.Receive() call will throw an exception after the timeout and I'm 20 | not sure if the socket is usable after that. 21 | 22 | * Implement/Test WORK_DATA handling between worker and client. 23 | 24 | * Implement WORK_WARNING 25 | 26 | * Implement GRAB_JOB_UNIQ / JOB_ASSIGN_UNIQ 27 | 28 | * Implement CANT_DO 29 | 30 | * Change GearmanClient to take a Task instead of all the arguments on SubmitJob/SubmitBackgroundJob. It will also make 31 | the API more similar to other language APIs. 32 | 33 | * Refactor how Connection/Protocol classes work. Perhaps the Client/WorkerProtocol classes could inherit from Connection? 34 | There's is a 1-to-1 relationship between a protocol and connection instance, so it could make sense. We would however need 35 | figure out how to share the connection manager, but still be able to create different protocol classes depending on if we're 36 | the worker or client. 37 | 38 | * Rake with Albacore for building and testing. 39 | -------------------------------------------------------------------------------- /lib/Newtonsoft.Json.License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 James Newton-King 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twingly/GearmanSharp/1db9bde59129943fde5b3eea67cb9f7720eb36ce/lib/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /lib/Rhino.Mocks.License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005 - 2008 Ayende Rahien (ayende@ayende.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of Ayende Rahien nor the names of its 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /lib/Rhino.Mocks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twingly/GearmanSharp/1db9bde59129943fde5b3eea67cb9f7720eb36ce/lib/Rhino.Mocks.dll -------------------------------------------------------------------------------- /lib/nunit.framework.License.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twingly/GearmanSharp/1db9bde59129943fde5b3eea67cb9f7720eb36ce/lib/nunit.framework.License.txt -------------------------------------------------------------------------------- /lib/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twingly/GearmanSharp/1db9bde59129943fde5b3eea67cb9f7720eb36ce/lib/nunit.framework.dll --------------------------------------------------------------------------------