├── .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
--------------------------------------------------------------------------------