├── .gitignore ├── LICENSE ├── README.md ├── dotnet ├── Metaparticle.Sync │ ├── Election.cs │ ├── Lock.cs │ ├── LockListener.cs │ └── Metaparticle.Sync.csproj ├── README.md └── examples │ ├── election │ ├── Dockerfile │ ├── ElectionMain.cs │ ├── deployment.yaml │ └── election.csproj │ └── lock │ ├── Dockerfile │ ├── LockMain.cs │ ├── deployment.yaml │ └── lock.csproj ├── go ├── README.md ├── examples │ ├── election │ │ └── election.go │ └── lock │ │ └── lock.go └── sync │ ├── client.go │ ├── election.go │ ├── election_test.go │ ├── lock.go │ └── lock_test.go ├── images └── metaparticle-sync.png ├── java ├── .gitignore ├── Dockerfile ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── metaparticle │ └── sync │ ├── Election.java │ ├── Lock.java │ ├── LockListener.java │ └── examples │ ├── ElectionMain.java │ └── Main.java ├── javascript ├── README.md ├── examples │ ├── Dockerfile │ ├── elector-deploy.yaml │ ├── elector.js │ ├── lock-deploy.yaml │ ├── lock.js │ └── package.json └── lib │ ├── README.md │ ├── election.js │ ├── index.js │ ├── lock.js │ └── package.json ├── overview.md ├── python ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── examples │ ├── election.py │ ├── election.yml │ ├── lock.py │ └── lock.yml ├── metaparticle_sync │ ├── __init__.py │ ├── election.py │ ├── lock.py │ └── version.py ├── setup.cfg └── setup.py └── rust ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── election │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs └── lock │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs └── src ├── election.rs ├── lib.rs └── lock.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | 7 | # Visual Studio Code 8 | .vscode 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | build/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | msbuild.log 28 | msbuild.err 29 | msbuild.wrn 30 | 31 | # Java stuff 32 | *.classpath 33 | *.settings 34 | *.project 35 | **/node_modules 36 | **/target 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Metaparticle Authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metaparticle Sync libraries. 2 | 3 | ## About the library 4 | Metaparticle is a standard library for cloud native development using containers and Kubernetes. 5 | 6 | The Metaparticle *sync* is a library within Metaparticle for synchronization across multiple containers 7 | running on different machines. 8 | 9 | ## Synchronization Primitives 10 | Currently the library supports two synchronization primitives: 11 | * Locks, for mutual exclusion of code between different containers on different machines. 12 | * Leader Election, for reliably selecting a single leader from a group of candidates, and failing over if that leader fails. 13 | 14 | ## Components 15 | Metaparticle sync is made up of two components: 16 | * A re-usable container that can be deployed as a side-car to implement synchronization operators 17 | * A collection of idiomatic client libraries that users can use in their applications to 18 | implement synchronization. Currently languages supported include: 19 | * [javascript (Nodejs)](javascript) 20 | * [python](python) 21 | * [java](java) 22 | * [C#/.NET Core](dotnet) 23 | 24 | ## Examples 25 | Examples for locking and leader election for each of the supported languages 26 | can be found in their respective directories. 27 | * [javascript](javascript/README.md) 28 | * [python](python/README.md) 29 | * [java](java/README.md) 30 | * [C#/.NET Core](dotnet/README.md) 31 | 32 | ## Details 33 | More technical details can be found in the [overview](overview.md). 34 | 35 | ## Contribute 36 | There are many ways to contribute to Metaparticle 37 | 38 | * [Submit bugs](https://github.com/metaparticle-io/package/issues) and help us verify fixes as they are checked in. 39 | * Review the source code changes. 40 | * Engage with other Metaparticle users and developers on [gitter](https://gitter.im/metaparticle-io/Lobby). 41 | * Join the #metaparticle discussion on [Twitter](https://twitter.com/MetaparticleIO). 42 | * [Contribute bug fixes](https://github.com/metaparticle-io/package/pulls). 43 | 44 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto://opencode@microsoft.com) with any additional questions or comments. 45 | 46 | -------------------------------------------------------------------------------- /dotnet/Metaparticle.Sync/Election.cs: -------------------------------------------------------------------------------- 1 | namespace Metaparticle.Sync { 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | public class Election : LockListener { 7 | private Lock lck; 8 | private Action electedAction; 9 | private Action terminateAction; 10 | // End signal is used to verify that the electedAction terminates properly when terminateAction 11 | // is called. 12 | private Object endSignal; 13 | private bool done; 14 | private bool running; 15 | 16 | public Election(string name, Action electedAction, Action terminateAction) : 17 | this(name, "http://localhost:13131", electedAction, terminateAction) {} 18 | 19 | public Election(string name, string baseUrl, Action electedAction, Action terminateAction) { 20 | this.lck = new Lock(name, baseUrl); 21 | this.lck.Listener = this; 22 | this.electedAction = electedAction; 23 | this.terminateAction = terminateAction; 24 | this.endSignal = new Object(); 25 | } 26 | 27 | public void Shutdown() { 28 | running = false; 29 | lock (lck) { 30 | Monitor.Pulse(lck); 31 | } 32 | } 33 | 34 | public void Run() { 35 | running = true; 36 | while (running) { 37 | lck.WaitOne(); 38 | lock (lck) { 39 | Monitor.Wait(lck); 40 | } 41 | } 42 | lck.Release(); 43 | } 44 | 45 | public void LockAcquired() { 46 | Task.Run(() => { 47 | done = false; 48 | electedAction(); 49 | lock (endSignal) { 50 | Monitor.Pulse(endSignal); 51 | done = true; 52 | } 53 | }); 54 | } 55 | 56 | public void LockLost() { 57 | lock (endSignal) { 58 | terminateAction(); 59 | // TODO: make this configurable? 60 | Task.Delay(1000).Wait(); 61 | if (!done) { 62 | Console.WriteLine("Master didn't terminate in expected time, force terminating."); 63 | //Application.exit(1); 64 | } 65 | } 66 | lock (lck) { 67 | Monitor.Pulse(lck); 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /dotnet/Metaparticle.Sync/Lock.cs: -------------------------------------------------------------------------------- 1 | namespace Metaparticle.Sync { 2 | using System; 3 | using System.Diagnostics; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | public class Lock { 10 | private Task maintainer; 11 | private string name; 12 | private bool running; 13 | private string baseUri; 14 | public LockListener Listener { set; get; } 15 | 16 | private static long WAIT_INTERVAL = 10 * 1000; 17 | 18 | public Lock(string name) : this(name, "http://localhost:13131") {} 19 | 20 | public Lock(string name, string baseUri) { 21 | this.name = name; 22 | this.baseUri = baseUri; 23 | this.Listener = null; 24 | } 25 | 26 | public void WaitOne() { 27 | while (!LockInternal(true, -1).Result); 28 | } 29 | 30 | public bool TryWait() { 31 | return LockInternal(false, 0).Result; 32 | } 33 | 34 | 35 | private bool AcquireLock() { 36 | HttpStatusCode code = HttpStatusCode.Unused; 37 | code = GetLock(name); 38 | if (code == HttpStatusCode.NotFound || code == HttpStatusCode.OK) { 39 | code = UpdateLock(name); 40 | } 41 | if (code == HttpStatusCode.OK) { 42 | HoldLock(name); 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | private async Task LockInternal(bool retry, long timeoutMillis) { 49 | Stopwatch watch = new Stopwatch(); 50 | watch.Start(); 51 | do { 52 | long sleep = WAIT_INTERVAL; 53 | lock(this) { 54 | if (maintainer != null) { 55 | throw new InvalidOperationException("Locks are not re-entrant!"); 56 | } 57 | if (AcquireLock()) { 58 | return true; 59 | } 60 | if (retry) { 61 | sleep = timeoutMillis - watch.ElapsedMilliseconds; 62 | if (timeoutMillis == -1 || sleep > WAIT_INTERVAL) { 63 | sleep = WAIT_INTERVAL; 64 | } 65 | } else { 66 | return false; 67 | } 68 | } 69 | await Task.Delay((int) sleep); 70 | } while (timeoutMillis == -1 || watch.ElapsedMilliseconds < timeoutMillis); 71 | return false; 72 | } 73 | 74 | public void Release() { 75 | lock(this) { 76 | if (maintainer == null) { 77 | throw new InvalidOperationException("Lock is not held."); 78 | } 79 | running = false; 80 | maintainer.Wait(10 * 1000); 81 | } 82 | } 83 | 84 | private HttpStatusCode GetLock(string name) { 85 | using (var client = new HttpClient()) { 86 | client.DefaultRequestHeaders.Accept.Clear(); 87 | client.DefaultRequestHeaders.Accept.Add( 88 | new MediaTypeWithQualityHeaderValue("application/json")); 89 | client.DefaultRequestHeaders.Add("User-Agent", "Metaparticle Sync Client"); 90 | var result = client.GetAsync(baseUri + "/locks/" + name).Result; 91 | return result.StatusCode; 92 | } 93 | } 94 | 95 | private HttpStatusCode UpdateLock(string name) { 96 | using (var client = new HttpClient()) { 97 | client.DefaultRequestHeaders.Accept.Clear(); 98 | client.DefaultRequestHeaders.Accept.Add( 99 | new MediaTypeWithQualityHeaderValue("application/json")); 100 | client.DefaultRequestHeaders.Add("User-Agent", "Metaparticle Sync Client"); 101 | var result = client.PutAsync(baseUri + "/locks/" + name, null).Result; 102 | return result.StatusCode; 103 | } 104 | } 105 | 106 | private void HoldLock(string name) { 107 | running = true; 108 | if (Listener != null) { 109 | Listener.LockAcquired(); 110 | } 111 | maintainer = Task.Run(async () => { 112 | while (running) { 113 | HttpStatusCode code = GetLock(name); 114 | if (code == HttpStatusCode.OK) { 115 | code = UpdateLock(name); 116 | } 117 | if (code != HttpStatusCode.OK) { 118 | Console.WriteLine("Unexpected status: " + code); 119 | if (Listener != null) { 120 | Listener.LockLost(); 121 | return; 122 | } else { 123 | // Environment.Exit(0); 124 | } 125 | } 126 | await Task.Delay(10 * 1000); 127 | } 128 | maintainer = null; 129 | if (Listener != null) { 130 | Listener.LockLost(); 131 | } 132 | }); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /dotnet/Metaparticle.Sync/LockListener.cs: -------------------------------------------------------------------------------- 1 | namespace Metaparticle.Sync { 2 | public interface LockListener { 3 | void LockAcquired(); 4 | void LockLost(); 5 | } 6 | } -------------------------------------------------------------------------------- /dotnet/Metaparticle.Sync/Metaparticle.Sync.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.2.0-beta 4 | The Metaparticle Project Authors 5 | 2017 The Metaparticle Project Authors 6 | Client library for the Metaparticle synchronization. 7 | https://opensource.org/licenses/MIT 8 | https://github.com/metaparticle-io/sync 9 | kubernetes;docker;containers; 10 | netstandard1.3 11 | k8s 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dotnet/README.md: -------------------------------------------------------------------------------- 1 | # Examples MetaParticle Sync library for .NET Core 2 | 3 | Metaparticle/Sync for .NET Core is a library that implements distributed synchronization 4 | for cloud-native applications using a container side-car and Kubernetes primitives. 5 | 6 | Metaparticle/Sync for .NET Core can be used for [locking](#locking-example) or for 7 | [leader election](#election-example) 8 | 9 | ## Adding the Library 10 | To add the `Metaparticle.Sync` library to your code you need to do two things: 11 | 12 | * Import the library, this is commonly done with: `using Metaparticle.Sync` 13 | * Run the `elector` side-car container. This is typically done via a Kubernetes Deployment (see examples below) 14 | 15 | ## Locking Example 16 | The simplest usage is to deploy mutual exclusion locks between different distributed components 17 | 18 | ### Code 19 | Here's the code for a simple locking example, that locks a lock named `test` and holds it for 45 seconds. 20 | 21 | ```cs 22 | namespace LockExample 23 | { 24 | using System; 25 | using System.Threading.Tasks; 26 | 27 | using Metaparticle.Sync; 28 | 29 | public class LockMain { 30 | public static void Main(string[] args) { 31 | Console.WriteLine("Locking"); 32 | var l = new Lock("test"); 33 | l.WaitOne(); 34 | Console.WriteLine("Acquired lock, waiting for 45 seconds."); 35 | Task.Delay(45 * 1000).Wait(); 36 | l.Release(); 37 | Console.WriteLine("Lock released."); 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | You'll notice that a lock is made up of three things: 44 | * A name (this should be unique for a cluster) 45 | * A callback function to be called when the lock is acquired. 46 | * An optional callback function to be called when the lock is lost. If this is not supplied, the program will forcibly exit in the (unlikely) case that a lock is lost. 47 | 48 | Simply creating a lock doesn't cause mutual exclusion. You also need to call `lock.WaitOne()`. When 49 | you are done, you call `lock.Release()` to release the lock. Locks have a TTL (time to live) so 50 | in the event of a failure, the lock will also be eventually lost. 51 | 52 | ### Deploying 53 | To deploy code using the `Metaparticle.Sync` package, you need to also include a side-car that 54 | does the heavy lifting for the lock. Your code and the sidecar should both be package as containers 55 | and then deployed as a Kubernetes Pod. 56 | 57 | Here is an example Kubernetes deployment: 58 | 59 | ```yaml 60 | apiVersion: extensions/v1beta1 61 | kind: Deployment 62 | metadata: 63 | labels: 64 | run: lock-dotnet 65 | name: lock-dotnet 66 | namespace: default 67 | spec: 68 | replicas: 2 69 | selector: 70 | matchLabels: 71 | run: lock-dotnet 72 | template: 73 | metadata: 74 | labels: 75 | run: lock-dotnet 76 | spec: 77 | containers: 78 | - image: brendanburns/elector 79 | name: elector 80 | - image: brendanburns/sync-dotnet 81 | name: example 82 | ``` 83 | 84 | You can create this with `kubectl create -f lock-deploy.yaml` which will create two different Pods, both of which are trying to obtain a lock named `test`. 85 | 86 | ## Election Example 87 | An extension of locking is _leader election_ where a leader is chosen from a group of replicas. 88 | This leader remains the leader for as long as it is healthy. If the leader ever fails, a new 89 | leader is chosen. This is an extremely useful pattern for implementing a variety of distributed systems. Generally leader election is performed for a named _shard_ which represents some piece 90 | of data to be owned/maintained by the leader. 91 | 92 | ### Code 93 | Implementing leader election in `Metaparticle.Sync` is simple, here is code that performs 94 | leader election for a shard named `test`. 95 | 96 | ```cs 97 | namespace ElectionExample 98 | { 99 | using System; 100 | using System.Threading; 101 | using System.Threading.Tasks; 102 | 103 | using Metaparticle.Sync; 104 | 105 | public class ElectionMain { 106 | public static void Main(string[] args) { 107 | var election = new Election( 108 | "test", 109 | () => { 110 | Console.WriteLine("I am the leader!"); 111 | }, 112 | () => { 113 | Console.WriteLine("I lost the leader!"); 114 | }); 115 | Console.WriteLine("Waiting for election"); 116 | election.Run(); 117 | } 118 | } 119 | } 120 | ``` 121 | 122 | ### Deploying leader election 123 | As with locking, you need to deploy the elector side-car to take advantage of `Metaparticle.Sync` elections. Here's an example Kubernetes Deployment which deploys three leader replicas: 124 | 125 | ```yaml 126 | apiVersion: extensions/v1beta1 127 | kind: Deployment 128 | metadata: 129 | labels: 130 | run: elector-dotnet 131 | name: elector-dotnet 132 | namespace: default 133 | spec: 134 | replicas: 3 135 | selector: 136 | matchLabels: 137 | run: elector-dotnet 138 | template: 139 | metadata: 140 | labels: 141 | run: elector-dotnet 142 | spec: 143 | containers: 144 | - image: brendanburns/elector 145 | imagePullPolicy: Always 146 | name: elector 147 | resources: {} 148 | # Replace the container below with your container. 149 | - image: brendanburns/dotnet-election 150 | name: example 151 | ``` 152 | 153 | ## Technical Details 154 | If you are interested in the technical details of how this all works, please see the [overview](../overview.md). -------------------------------------------------------------------------------- /dotnet/examples/election/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:9 2 | RUN apt-get update && apt-get -qq -y install libunwind8 libicu57 libssl1.0 liblttng-ust0 libcurl3 libuuid1 libkrb5-3 zlib1g 3 | COPY bin/release/netcoreapp2.0/debian.8-x64/publish/* /exe/ 4 | CMD /exe/election 5 | -------------------------------------------------------------------------------- /dotnet/examples/election/ElectionMain.cs: -------------------------------------------------------------------------------- 1 | namespace ElectionExample 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using Metaparticle.Sync; 8 | 9 | public class ElectionMain { 10 | public static void Main(string[] args) { 11 | var election = new Election( 12 | "test", 13 | () => { 14 | Console.WriteLine("I am the leader!"); 15 | }, 16 | () => { 17 | Console.WriteLine("I lost the leader!"); 18 | }); 19 | Console.WriteLine("Waiting for election"); 20 | election.Run(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /dotnet/examples/election/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: elector-dotnet 6 | name: elector-dotnet 7 | namespace: default 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | run: elector-dotnet 13 | template: 14 | metadata: 15 | labels: 16 | run: elector-dotnet 17 | spec: 18 | containers: 19 | - image: brendanburns/elector 20 | imagePullPolicy: Always 21 | name: elector 22 | resources: {} 23 | # Replace the container below with your container. 24 | - image: brendanburns/dotnet-election 25 | name: example 26 | -------------------------------------------------------------------------------- /dotnet/examples/election/election.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.2.0-beta 4 | The Metaparticle Project Authors 5 | 2017 The Metaparticle Project Authors 6 | Client library for the Metaparticle synchronization. 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | https://github.com/metaparticle-io/sync 9 | kubernetes;docker;containers; 10 | netcoreapp2.0 11 | Metaparticle.Sync 12 | Exe 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dotnet/examples/lock/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:9 2 | RUN apt-get update && apt-get -qq -y install libunwind8 libicu57 libssl1.0 liblttng-ust0 libcurl3 libuuid1 libkrb5-3 zlib1g 3 | COPY bin/release/netcoreapp2.0/debian.8-x64/publish/* /exe/ 4 | CMD /exe/lock 5 | -------------------------------------------------------------------------------- /dotnet/examples/lock/LockMain.cs: -------------------------------------------------------------------------------- 1 | namespace LockExample 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | using Metaparticle.Sync; 7 | 8 | public class LockMain { 9 | public static void Main(string[] args) { 10 | Console.WriteLine("Locking"); 11 | var l = new Lock("test"); 12 | l.WaitOne(); 13 | Console.WriteLine("Acquired lock, waiting for 45 seconds."); 14 | Task.Delay(45 * 1000).Wait(); 15 | l.Release(); 16 | Console.WriteLine("Lock released."); 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /dotnet/examples/lock/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: lock-js 6 | name: lock-js 7 | namespace: default 8 | spec: 9 | replicas: 2 10 | selector: 11 | matchLabels: 12 | run: lock-js 13 | template: 14 | metadata: 15 | labels: 16 | run: lock-js 17 | spec: 18 | containers: 19 | - image: brendanburns/elector 20 | name: elector 21 | - image: brendanburns/sync-dotnet 22 | name: example 23 | -------------------------------------------------------------------------------- /dotnet/examples/lock/lock.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.2.0-beta 4 | The Metaparticle Project Authors 5 | 2017 The Metaparticle Project Authors 6 | Client library for the Metaparticle synchronization. 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | https://github.com/metaparticle-io/sync 9 | kubernetes;docker;containers; 10 | netcoreapp2.0 11 | Metaparticle.Sync 12 | Exe 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /go/README.md: -------------------------------------------------------------------------------- 1 | # Metaparticle/Sync go library 2 | 3 | Metaparticle/Sync for go is a library that implements distributed synchronization for 4 | cloud-native applications using a container sidecar and Kubernetes primitives. 5 | 6 | 7 | ## Quickstart 8 | 9 | ``` 10 | import ( 11 | "context" 12 | "time" 13 | 14 | "github.com/metaparticle-io/sync/go/sync" 15 | ) 16 | 17 | fn main() { 18 | lock := mpsync.NewLock("some-lock", mpsync.DEFAULT_INTERVAL) 19 | lock.Lock(context.Background(), func(c context.Context) error { 20 | fmt.Println("Hello, World!") 21 | time.Sleep(time.Second * 15) 22 | }) 23 | } 24 | 25 | ## License 26 | 27 | Licensed under Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 28 | -------------------------------------------------------------------------------- /go/examples/election/election.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/metaparticle-io/sync/go/sync" 9 | ) 10 | 11 | func main() { 12 | lock := mpsync.NewLock("election-lock", mpsync.DEFAULT_INTERVAL) 13 | election := mpsync.NewElection(lock, 14 | func(c context.Context) error { 15 | fmt.Println("LEADER!") 16 | time.Sleep(time.Second * 45) 17 | return nil 18 | }, 19 | func(c context.Context) error { 20 | fmt.Println("FOLLOWER") 21 | return nil 22 | }) 23 | 24 | _ = election.Run(context.Background()) 25 | time.Sleep(time.Second * 60) 26 | } 27 | -------------------------------------------------------------------------------- /go/examples/lock/lock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/metaparticle-io/sync/go/sync" 9 | ) 10 | 11 | func onLock(c context.Context) error { 12 | fmt.Println("Got the lock!") 13 | time.Sleep(time.Second * 45) 14 | return nil 15 | } 16 | 17 | func main() { 18 | lock := mpsync.NewLock("just-a-lock", mpsync.DEFAULT_INTERVAL) 19 | if err := lock.Lock(context.Background(), onLock); err != nil { 20 | fmt.Printf("unexpected lock error: %s", err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /go/sync/client.go: -------------------------------------------------------------------------------- 1 | package mpsync 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | ) 8 | 9 | // MockableClient - HTTP interface to metaparticle-sync sidecar container. 10 | type MockableClient interface { 11 | Get(url.URL) (int, error) 12 | Put(url.URL) (int, error) 13 | } 14 | 15 | type httpClient struct { 16 | http.Client 17 | } 18 | 19 | func (h httpClient) Get(url url.URL) (int, error) { 20 | req, _ := http.NewRequest("GET", url.String(), nil) 21 | 22 | resp, err := h.Do(req) 23 | if err != nil { 24 | return -1, err 25 | } 26 | 27 | return resp.StatusCode, nil 28 | } 29 | 30 | func (h httpClient) Put(url url.URL) (int, error) { 31 | req, _ := http.NewRequest("PUT", url.String(), nil) 32 | 33 | resp, err := h.Do(req) 34 | if err != nil { 35 | return -1, err 36 | } 37 | 38 | if resp.StatusCode == 200 || resp.StatusCode == 409 { 39 | return resp.StatusCode, nil 40 | } 41 | 42 | return -1, fmt.Errorf("Unexpected status: %d", resp.StatusCode) 43 | } 44 | -------------------------------------------------------------------------------- /go/sync/election.go: -------------------------------------------------------------------------------- 1 | package mpsync 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // NewElection - return a new election. 8 | func NewElection(locker Locker, leaderFn WorkFn, followerFn WorkFn) Election { 9 | return Election{ 10 | locker: locker, 11 | leaderFn: leaderFn, 12 | followerFn: followerFn, 13 | } 14 | } 15 | 16 | // Election - type representing a metaparticle-sync election. 17 | type Election struct { 18 | locker Locker 19 | leaderFn WorkFn 20 | followerFn WorkFn 21 | } 22 | 23 | // Run - Run the election. 24 | func (e Election) Run(ctx context.Context) error { 25 | if err := e.locker.Lock(ctx, e.leaderFn); err != nil { 26 | return e.followerFn(ctx) 27 | } 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /go/sync/election_test.go: -------------------------------------------------------------------------------- 1 | package mpsync_test 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/metaparticle-io/sync/go/sync" 10 | ) 11 | 12 | func TestElection(t *testing.T) { 13 | server := Server{} 14 | 15 | lock0 := mpsync.NewLock("super-important-lock", 100) 16 | lock1 := mpsync.NewLock("super-important-lock", 100) 17 | lock2 := mpsync.NewLock("super-important-lock", 100) 18 | 19 | lock0.SetClient(newClient("client0", &server)) 20 | lock1.SetClient(newClient("client1", &server)) 21 | lock2.SetClient(newClient("client2", &server)) 22 | 23 | lChan := make(chan string, 1) 24 | fChan := make(chan string, 2) 25 | 26 | election0 := mpsync.NewElection(lock0, 27 | func(c context.Context) error { 28 | lChan <- "lock0" 29 | time.Sleep(time.Millisecond * 250) 30 | return nil 31 | }, 32 | func(c context.Context) error { 33 | fChan <- "lock0" 34 | time.Sleep(time.Millisecond * 250) 35 | return nil 36 | }) 37 | election1 := mpsync.NewElection(lock1, 38 | func(c context.Context) error { 39 | lChan <- "lock1" 40 | time.Sleep(time.Millisecond * 250) 41 | return nil 42 | }, 43 | func(c context.Context) error { 44 | fChan <- "lock1" 45 | time.Sleep(time.Millisecond * 250) 46 | return nil 47 | }) 48 | election2 := mpsync.NewElection(lock2, 49 | func(c context.Context) error { 50 | lChan <- "lock2" 51 | time.Sleep(time.Millisecond * 250) 52 | return nil 53 | }, 54 | func(c context.Context) error { 55 | fChan <- "lock2" 56 | time.Sleep(time.Millisecond * 250) 57 | return nil 58 | }) 59 | 60 | var wg sync.WaitGroup 61 | errors := make(chan error, 10) 62 | electionFn := func(e mpsync.Election) { 63 | defer wg.Done() 64 | errors <- e.Run(context.Background()) 65 | } 66 | 67 | wg.Add(3) 68 | go electionFn(election0) 69 | go electionFn(election1) 70 | go electionFn(election2) 71 | 72 | wg.Wait() 73 | 74 | leader := <-lChan 75 | if leader != "lock2" { 76 | t.Errorf("expected lock2 to be leader, got %s instead", leader) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /go/sync/lock.go: -------------------------------------------------------------------------------- 1 | package mpsync 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const ( 13 | // OK - Lock exists or we hold it. 14 | OK = 200 15 | 16 | // NOTFOUND - Lock does not exist. 17 | NOTFOUND = 404 18 | 19 | // CONFLICT - Lock is held by another caller. 20 | CONFLICT = 409 21 | 22 | // BaseLockURL - The URL of the sidecar container. 23 | BaseLockURL = "http://localhost:8080/" 24 | 25 | // DEFAULT_INTERVAL - default to 10 seconds interval between heartbeats. 26 | DEFAULT_INTERVAL = 10000 27 | ) 28 | 29 | // WorkFn - A function representing a particle of work to be done when 30 | // the metaparticle-sync lock is taken. 31 | type WorkFn func(context.Context) error 32 | 33 | type retryKey string 34 | 35 | // WithRetry - A context wrapped with a retry Value. 36 | func WithRetry(ctx context.Context, retries int) context.Context { 37 | return context.WithValue(ctx, retryKey("retries"), retries) 38 | } 39 | 40 | // Locker - Interface that Locks. 41 | type Locker interface { 42 | Lock(context.Context, WorkFn) error 43 | } 44 | 45 | // Lock - 46 | type Lock struct { 47 | client MockableClient 48 | isLocked bool 49 | interval int64 50 | lock string 51 | lockURL url.URL 52 | } 53 | 54 | // NewLock - Create a new lock. 55 | func NewLock(lockStr string, interval int64) *Lock { 56 | baseURL, _ := url.Parse(BaseLockURL) 57 | lock, _ := url.Parse("locks/" + lockStr) 58 | lockURL := baseURL.ResolveReference(lock) 59 | 60 | return &Lock{ 61 | client: httpClient{ 62 | http.Client{ 63 | Timeout: time.Second * 10, 64 | }}, 65 | isLocked: false, 66 | interval: interval, 67 | lock: lockStr, 68 | lockURL: *lockURL, 69 | } 70 | } 71 | 72 | // Lock - 73 | func (l *Lock) Lock(ctx context.Context, work WorkFn) error { 74 | if l.Locked() { 75 | return fmt.Errorf("locks are not reentrant") 76 | } 77 | code, err := l.client.Get(l.lockURL) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | switch code { 83 | case OK, NOTFOUND: 84 | retries := ctx.Value(retryKey("retries")) 85 | if retries == nil { 86 | retries = 1 87 | } 88 | 89 | attempts := retries.(int) 90 | 91 | for { 92 | if attempts < 1 && attempts != -1 { 93 | return fmt.Errorf("could not obtain a lock after %d attempt(s)", retries) 94 | } 95 | 96 | code, err := l.client.Put(l.lockURL) 97 | if err != nil { 98 | // LOG 99 | return err 100 | } 101 | 102 | switch code { 103 | case OK: 104 | var wg sync.WaitGroup 105 | 106 | err := make(chan error, 1) 107 | heartbeatCtx, heartbeatCancel := context.WithCancel(ctx) 108 | workerCtx, workerCancel := context.WithCancel(ctx) 109 | 110 | heartbeat := func(c context.Context) { 111 | defer wg.Done() 112 | defer workerCancel() 113 | 114 | for { 115 | select { 116 | case <-c.Done(): 117 | return 118 | default: 119 | time.Sleep(time.Millisecond * time.Duration(l.interval)) 120 | code, err := l.client.Put(l.lockURL) 121 | if err != nil { 122 | // TODO - log 123 | return 124 | } 125 | switch code { 126 | case OK: 127 | case CONFLICT: 128 | // We lost the lock. 129 | return 130 | } 131 | } 132 | } 133 | } 134 | 135 | worker := func(c context.Context, e chan error) { 136 | defer wg.Done() 137 | defer heartbeatCancel() 138 | 139 | e <- work(c) 140 | } 141 | 142 | l.isLocked = true 143 | 144 | wg.Add(1) 145 | go heartbeat(heartbeatCtx) 146 | 147 | wg.Add(1) 148 | go worker(workerCtx, err) 149 | 150 | wg.Wait() 151 | 152 | return <-err 153 | case CONFLICT: 154 | // Try again 155 | if attempts != -1 { 156 | attempts-- 157 | } 158 | time.Sleep(time.Millisecond * time.Duration(l.interval)) 159 | default: 160 | return fmt.Errorf("Unexpected status: %d", code) 161 | } 162 | } 163 | default: 164 | return fmt.Errorf("Unexpected status: %d", code) 165 | } 166 | } 167 | 168 | // Locked - Return whether or not the lock was successfully taken. 169 | func (l *Lock) Locked() bool { 170 | return l.isLocked 171 | } 172 | 173 | // SetClient - Helper for test mocks without having the tests be part of package. 174 | func (l *Lock) SetClient(client MockableClient) { 175 | l.client = client 176 | } 177 | 178 | type heartbeat struct { 179 | interval uint 180 | } 181 | -------------------------------------------------------------------------------- /go/sync/lock_test.go: -------------------------------------------------------------------------------- 1 | package mpsync_test 2 | 3 | import ( 4 | "context" 5 | "net/url" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/metaparticle-io/sync/go/sync" 11 | ) 12 | 13 | type MockLock struct { 14 | client string 15 | deadline time.Time 16 | } 17 | 18 | type Server struct { 19 | locks sync.Map 20 | } 21 | 22 | type client struct { 23 | name string 24 | server *Server 25 | } 26 | 27 | func newClient(name string, server *Server) client { 28 | return client{ 29 | name: name, 30 | server: server, 31 | } 32 | } 33 | 34 | func (c client) Get(url url.URL) (int, error) { 35 | _, ok := c.server.locks.Load(url.String()) 36 | if !ok { 37 | return 404, nil 38 | } 39 | return 200, nil 40 | } 41 | 42 | func (c client) Put(url url.URL) (int, error) { 43 | value, ok := c.server.locks.Load(url.String()) 44 | if ok { 45 | // Lock exists. 46 | // This lock isn't held by this client and the lock isn't up. 47 | lock := value.(MockLock) 48 | if lock.client != c.name && !time.Now().After(lock.deadline) { 49 | return 409, nil 50 | } 51 | // It's either OUR lock or we're after the deadline. 52 | c.server.locks.Delete(url.String()) 53 | } 54 | 55 | _, loaded := c.server.locks.LoadOrStore(url.String(), 56 | MockLock{ 57 | client: c.name, 58 | deadline: time.Now().Add(time.Millisecond * 100), 59 | }) 60 | if loaded { 61 | return 409, nil 62 | } 63 | 64 | return 200, nil 65 | } 66 | 67 | func TestLockingWithoutRetrying(t *testing.T) { 68 | workFn := func(_ context.Context) error { 69 | time.Sleep(time.Millisecond * 500) 70 | return nil 71 | } 72 | 73 | server := Server{} 74 | 75 | lock0 := mpsync.NewLock("super-important-lock", 100) 76 | lock1 := mpsync.NewLock("super-important-lock", 100) 77 | 78 | lock0.SetClient(newClient("client0", &server)) 79 | lock1.SetClient(newClient("client1", &server)) 80 | 81 | if err := lock0.Lock(context.Background(), workFn); err != nil { 82 | t.Errorf("workFn() failed unexpectedly! %+v", err) 83 | } 84 | 85 | if lock0.Locked() == false { 86 | t.Error("Expected lock0 to be locked!") 87 | } 88 | 89 | err := lock1.Lock(context.Background(), workFn) 90 | if err.Error() != "could not obtain a lock after 1 attempt(s)" { 91 | t.Errorf("unexpected error on failed lock: %s", err) 92 | } 93 | 94 | if lock1.Locked() == true { 95 | t.Error("Expected lock1 not to be locked!") 96 | } 97 | } 98 | 99 | func TestLockingWithRetries(t *testing.T) { 100 | workFn := func(_ context.Context) error { 101 | time.Sleep(time.Millisecond * 250) 102 | return nil 103 | } 104 | 105 | server := Server{} 106 | 107 | retryContext := mpsync.WithRetry(context.Background(), 5) 108 | 109 | lock0 := mpsync.NewLock("super-important-lock", 100) 110 | lock1 := mpsync.NewLock("super-important-lock", 100) 111 | 112 | lock0.SetClient(newClient("client0", &server)) 113 | lock1.SetClient(newClient("client1", &server)) 114 | 115 | if err := lock0.Lock(retryContext, workFn); err != nil { 116 | t.Errorf("workFn() failed unexpectedly! %+v", err) 117 | } 118 | 119 | if err := lock1.Lock(retryContext, workFn); err != nil { 120 | t.Errorf("workFn() failed unexpectedly! %+v", err) 121 | } 122 | 123 | if lock0.Locked() == false { 124 | t.Error("Expected lock0 to be locked!") 125 | } 126 | 127 | if lock1.Locked() == false { 128 | t.Error("Expected lock1 to be locked!") 129 | } 130 | } 131 | 132 | func TestLockingWithInfiniteRetries(t *testing.T) { 133 | workFn := func(_ context.Context) error { 134 | time.Sleep(time.Millisecond * 250) 135 | return nil 136 | } 137 | 138 | server := Server{} 139 | 140 | retryContext := mpsync.WithRetry(context.Background(), -1) 141 | 142 | lock0 := mpsync.NewLock("super-important-lock", 100) 143 | lock1 := mpsync.NewLock("super-important-lock", 100) 144 | 145 | lock0.SetClient(newClient("client0", &server)) 146 | lock1.SetClient(newClient("client1", &server)) 147 | 148 | if err := lock0.Lock(retryContext, workFn); err != nil { 149 | t.Errorf("workFn() failed unexpectedly! %+v", err) 150 | } 151 | 152 | if err := lock1.Lock(retryContext, workFn); err != nil { 153 | t.Errorf("workFn() failed unexpectedly! %+v", err) 154 | } 155 | 156 | if lock0.Locked() == false { 157 | t.Error("Expected lock0 to be locked!") 158 | } 159 | 160 | if lock1.Locked() == false { 161 | t.Error("Expected lock1 to be locked!") 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /images/metaparticle-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metaparticle-io/sync/4d422ed78b49f2827bd912933db377c9a1b3a2d3/images/metaparticle-sync.png -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | target/** 2 | -------------------------------------------------------------------------------- /java/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-alpine 2 | COPY target/sync-0.0.1-alpha1-SNAPSHOT-jar-with-dependencies.jar /main.jar 3 | CMD java -classpath /main.jar io.metaparticle.sync.examples.Main 4 | -------------------------------------------------------------------------------- /java/README.md: -------------------------------------------------------------------------------- 1 | # Examples MetaParticle Sync library for Java 2 | 3 | Metaparticle/Sync for Java is a library that implements distributed synchronization 4 | for cloud-native applications using a container side-car and Kubernetes primitives. 5 | 6 | Metaparticle/Sync for Java can be used for [locking](#locking-example) or for 7 | [leader election](#election-example) 8 | 9 | ## Adding the Library 10 | To add the `io.metaparticle.sync` library to your code you need to do two things: 11 | 12 | * Import the library: `import io.metaparticle.sync.*;` 13 | * Run the `elector` side-car container. This is typically done via a Kubernetes Deployment 14 | (see [examples](#deploying) below) 15 | 16 | ## Locking Example 17 | The simplest usage is to deploy mutual exclusion locks between different distributed components 18 | 19 | ### Code 20 | Here's the code for a simple locking example, that locks a lock named `test` and holds it for 45 seconds. 21 | 22 | ```java 23 | import io.metaparticle.sync.Lock; 24 | 25 | public class Main { 26 | public static void main(String[] args) throws InterruptedException { 27 | Lock lock = new Lock("test"); 28 | System.out.println("Locking."); 29 | lock.lock(); 30 | System.out.println("Sleeping."); 31 | Thread.sleep(45 * 1000); 32 | System.out.println("Unlocking."); 33 | lock.unlock(); 34 | } 35 | } 36 | ``` 37 | 38 | You'll notice that a lock requires a name. This name should be unique for a cluster. 39 | 40 | Simply creating a lock doesn't cause mutual exclusion. You also need to call `lock.lock()`. When 41 | you are done, you call `lock.unlock()` to release the lock. Locks have a TTL (time to live) so 42 | in the event of a failure, the lock will also be eventually lost. 43 | 44 | Locks implement the `java.util.concurrent.Lock` interface and are thus compatible with locks any 45 | where in a Java program. 46 | 47 | ### Deploying 48 | To deploy code using the `io.metaparticle.sync` package, you need to also include a side-car that 49 | does the heavy lifting for the lock. Your code and the sidecar should both be package as containers 50 | and then deployed as a Kubernetes Pod. 51 | 52 | Here is an example Kubernetes deployment: 53 | 54 | ```yaml 55 | apiVersion: extensions/v1beta1 56 | kind: Deployment 57 | metadata: 58 | labels: 59 | run: lock-java 60 | name: lock-java 61 | namespace: default 62 | spec: 63 | replicas: 2 64 | selector: 65 | matchLabels: 66 | run: lock-java 67 | template: 68 | metadata: 69 | labels: 70 | run: lock-java 71 | spec: 72 | containers: 73 | - image: brendanburns/elector 74 | name: elector 75 | - image: brendanburns/sync-java 76 | name: example 77 | ``` 78 | 79 | You can create this with `kubectl create -f lock-deploy.yaml` which will create two different Pods, both of which are trying to obtain a lock named `test`. 80 | 81 | ## Election Example 82 | An extension of locking is _leader election_ where a leader is chosen from a group of replicas. 83 | This leader remains the leader for as long as it is healthy. If the leader ever fails, a new 84 | leader is chosen. This is an extremely useful pattern for implementing a variety of distributed systems. Generally leader election is performed for a named _shard_ which represents some piece 85 | of data to be owned/maintained by the leader. 86 | 87 | ### Code 88 | Implementing leader election in `io.metaparticle.sync` is simple, here is code that performs 89 | leader election for a shard named `test`. 90 | 91 | ```java 92 | import io.metaparticle.sync.Election; 93 | 94 | public class ElectionMain { 95 | public static void main(String[] args) throws InterruptedException { 96 | Election e = new Election("test"); 97 | e.addMasterListener(() -> { 98 | System.out.println("I am the master."); 99 | // <-- Do something as master here --> 100 | }); 101 | e.addMasterLostListener(() -> { 102 | System.out.println("I lost the master."); 103 | // <-- Handle losing the master here --> 104 | }); 105 | } 106 | ``` 107 | 108 | ### Deploying leader election 109 | As with locking, you need to deploy the elector side-car to take advantage of `io.metaparticle.sync` elections. Here's an example Kubernetes Deployment which deploys three leader replicas: 110 | 111 | ```yaml 112 | apiVersion: extensions/v1beta1 113 | kind: Deployment 114 | metadata: 115 | labels: 116 | run: elector-java 117 | name: elector-java 118 | namespace: default 119 | spec: 120 | replicas: 3 121 | selector: 122 | matchLabels: 123 | run: elector-java 124 | template: 125 | metadata: 126 | labels: 127 | run: elector-java 128 | spec: 129 | containers: 130 | - image: brendanburns/elector 131 | name: elector 132 | # Replace the container below with your container. 133 | - image: brendanburns/sync-java 134 | name: example 135 | command: 136 | - java 137 | - -classpath 138 | - /main.jar 139 | - io.metaparticle.sync.examples.ElectionMain 140 | ``` 141 | 142 | ## Technical Details 143 | If you are interested in the technical details of how this all works, please see the [overview](../overview.md). -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | io.metaparticle 6 | sync 7 | 0.0.1-alpha1-SNAPSHOT 8 | jar 9 | metaparticle-sync 10 | https://github.com/metaparticle-io/sync 11 | 12 | 13 | com.mashape.unirest 14 | unirest-java 15 | 1.4.9 16 | 17 | 18 | 19 | 20 | 21 | org.apache.maven.plugins 22 | maven-compiler-plugin 23 | 3.1 24 | 25 | ${java.version} 26 | ${java.version} 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-assembly-plugin 32 | 3.1.0 33 | 34 | 35 | jar-with-dependencies 36 | 37 | 38 | 39 | 40 | assemble-all 41 | package 42 | 43 | single 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 1.8 52 | ${java.version} 53 | ${java.version} 54 | 1.7.7 55 | 56 | 57 | -------------------------------------------------------------------------------- /java/src/main/java/io/metaparticle/sync/Election.java: -------------------------------------------------------------------------------- 1 | package io.metaparticle.sync; 2 | 3 | public class Election implements LockListener, Runnable { 4 | private Lock lock; 5 | private Runnable electedAction; 6 | private Runnable terminateAction; 7 | // End signal is used to verify that the electedAction terminates properly when terminateAction 8 | // is called. 9 | private Object endSignal; 10 | private boolean done; 11 | private boolean running; 12 | 13 | public Election(String name, Runnable electedAction, Runnable terminateAction) { 14 | this(name, "http://localhost:13131", electedAction, terminateAction); 15 | } 16 | 17 | public Election(String name, String baseUrl, Runnable electedAction, Runnable terminateAction) { 18 | this.lock = new Lock(name, baseUrl); 19 | this.lock.setLockListener(this); 20 | this.electedAction = electedAction; 21 | this.terminateAction = terminateAction; 22 | this.endSignal = new Object(); 23 | } 24 | 25 | public void shutdown() { 26 | running = false; 27 | synchronized (lock) { 28 | lock.notify(); 29 | } 30 | } 31 | 32 | @Override 33 | public void run() { 34 | running = true; 35 | while (running) { 36 | try { 37 | lock.lock(); 38 | synchronized (lock) { 39 | lock.wait(); 40 | } 41 | } catch (InterruptedException ex) { 42 | ex.printStackTrace(); 43 | } 44 | } 45 | lock.unlock(); 46 | } 47 | 48 | @Override 49 | public void lockAcquired() { 50 | new Thread(new Runnable() { 51 | public void run() { 52 | done = false; 53 | electedAction.run(); 54 | synchronized (endSignal) { 55 | endSignal.notify(); 56 | done = true; 57 | } 58 | } 59 | }).start(); 60 | } 61 | 62 | @Override 63 | public void lockLost() { 64 | synchronized (endSignal) { 65 | terminateAction.run(); 66 | // TODO: make this configurable? 67 | try { 68 | endSignal.wait(1000); 69 | } catch (InterruptedException ex) { 70 | ex.printStackTrace(); 71 | } 72 | if (!done) { 73 | System.err.println("Master didn't terminate in expected time, force terminating."); 74 | System.exit(1); 75 | } 76 | } 77 | synchronized (lock) { 78 | lock.notify(); 79 | } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /java/src/main/java/io/metaparticle/sync/Lock.java: -------------------------------------------------------------------------------- 1 | package io.metaparticle.sync; 2 | 3 | import com.mashape.unirest.http.HttpResponse; 4 | import com.mashape.unirest.http.JsonNode; 5 | import com.mashape.unirest.http.Unirest; 6 | import com.mashape.unirest.http.exceptions.UnirestException; 7 | import java.io.IOException; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.locks.Condition; 10 | 11 | public class Lock implements java.util.concurrent.locks.Lock { 12 | // TODO: Switch to a threadpool here? 13 | private Thread maintainer; 14 | private String name; 15 | private boolean running; 16 | private String baseUri; 17 | private LockListener listener; 18 | 19 | private static long WAIT_INTERVAL = 10 * 1000; 20 | 21 | public Lock(String name) { 22 | this(name, "http://localhost:13131"); 23 | } 24 | 25 | public Lock(String name, String baseUri) { 26 | this.name = name; 27 | this.baseUri = baseUri; 28 | this.listener = null; 29 | } 30 | 31 | public void setLockListener(LockListener l) { 32 | listener = l; 33 | } 34 | 35 | @Override 36 | public void lock() { 37 | while (true) { 38 | try { 39 | lockInterruptibly(); 40 | return; 41 | } catch (InterruptedException ignore) {} 42 | } 43 | } 44 | 45 | @Override 46 | public void lockInterruptibly() throws InterruptedException { 47 | lockInternal(true, -1); 48 | } 49 | 50 | @Override 51 | public boolean tryLock() { 52 | try { 53 | return lockInternal(false, 0); 54 | } catch (InterruptedException ex) { 55 | // This can never actually happen on this code-path. 56 | } 57 | return false; 58 | } 59 | 60 | @Override 61 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 62 | return lockInternal(true, TimeUnit.MILLISECONDS.convert(time, unit)); 63 | } 64 | 65 | private boolean acquireLock() { 66 | int code = -1; 67 | try { 68 | code = getLock(name); 69 | if (code == 404 || code == 200) { 70 | code = updateLock(name); 71 | } 72 | if (code == 200) { 73 | holdLock(name); 74 | return true; 75 | } 76 | } catch (IOException ex) { 77 | ex.printStackTrace(); 78 | } 79 | return false; 80 | } 81 | 82 | private synchronized boolean lockInternal(boolean retry, long timeoutMillis) throws InterruptedException { 83 | if (maintainer != null) { 84 | throw new IllegalStateException("Locks are not re-entrant!"); 85 | } 86 | long deadline = System.currentTimeMillis() + timeoutMillis; 87 | do { 88 | if (acquireLock()) { 89 | return true; 90 | } 91 | if (retry) { 92 | long sleep = deadline - System.currentTimeMillis(); 93 | if (timeoutMillis == -1 || (deadline - System.currentTimeMillis()) > WAIT_INTERVAL) { 94 | sleep = WAIT_INTERVAL; 95 | } 96 | Thread.sleep(sleep); 97 | } else { 98 | return false; 99 | } 100 | } while (timeoutMillis == -1 || System.currentTimeMillis() < deadline); 101 | return false; 102 | } 103 | 104 | @Override 105 | public Condition newCondition() { 106 | // TODO: try to support this? 107 | throw new UnsupportedOperationException("unsupported."); 108 | } 109 | 110 | @Override 111 | public synchronized void unlock() { 112 | if (maintainer == null) { 113 | throw new IllegalStateException("Lock is not held."); 114 | } 115 | running = false; 116 | try { 117 | maintainer.join(10 * 1000); 118 | } catch (InterruptedException ex) { 119 | ex.printStackTrace(); 120 | } 121 | } 122 | 123 | private int getLock(String name) throws IOException { 124 | try { 125 | HttpResponse jsonResponse = Unirest.get(baseUri + "/locks/" + name) 126 | .header("accept", "application/json") 127 | .asJson(); 128 | return jsonResponse.getStatus(); 129 | } catch (UnirestException ex) { 130 | throw new IOException(ex); 131 | } 132 | } 133 | 134 | private int updateLock(String name) throws IOException { 135 | try { 136 | HttpResponse jsonResponse = Unirest.put(baseUri + "/locks/" + name) 137 | .header("accept", "application/json") 138 | .asJson(); 139 | return jsonResponse.getStatus(); 140 | } catch (UnirestException ex) { 141 | throw new IOException(ex); 142 | } 143 | } 144 | 145 | private void holdLock(final String name) { 146 | running = true; 147 | if (listener != null) { 148 | listener.lockAcquired(); 149 | } 150 | maintainer = new Thread(new Runnable() { 151 | public void run() { 152 | while (running) { 153 | try { 154 | int code = getLock(name); 155 | if (code == 200) { 156 | code = updateLock(name); 157 | } 158 | if (code != 200) { 159 | System.out.println("Unexpected status: " + code); 160 | if (listener != null) { 161 | listener.lockLost(); 162 | return; 163 | } else { 164 | System.exit(0); 165 | } 166 | } 167 | Thread.sleep(10 * 1000); 168 | } catch (IOException | InterruptedException ex) { 169 | ex.printStackTrace(); 170 | } 171 | } 172 | maintainer = null; 173 | if (listener != null) { 174 | listener.lockLost(); 175 | } 176 | } 177 | }); 178 | maintainer.start(); 179 | } 180 | } -------------------------------------------------------------------------------- /java/src/main/java/io/metaparticle/sync/LockListener.java: -------------------------------------------------------------------------------- 1 | package io.metaparticle.sync; 2 | 3 | public interface LockListener { 4 | public void lockAcquired(); 5 | public void lockLost(); 6 | } -------------------------------------------------------------------------------- /java/src/main/java/io/metaparticle/sync/examples/ElectionMain.java: -------------------------------------------------------------------------------- 1 | package io.metaparticle.sync.examples; 2 | 3 | import io.metaparticle.sync.Election; 4 | 5 | import java.util.Random; 6 | 7 | public class ElectionMain { 8 | public static void main(String[] args) throws InterruptedException { 9 | Random r = new Random(); 10 | while (true) { 11 | final Object block = new Object(); 12 | Election e = new Election( 13 | "test", args[0], 14 | () -> { 15 | System.out.println("I am the master."); 16 | synchronized(block) { 17 | try { 18 | block.wait(); 19 | } catch (InterruptedException ex) { 20 | ex.printStackTrace(); 21 | } 22 | } 23 | }, 24 | () -> { 25 | System.out.println("I lost the master."); 26 | synchronized(block) { 27 | block.notify(); 28 | } 29 | }); 30 | new Thread(e).start(); 31 | Thread.sleep((r.nextInt(15) + 25) * 1000); 32 | e.shutdown(); 33 | Thread.sleep(10 * 1000); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /java/src/main/java/io/metaparticle/sync/examples/Main.java: -------------------------------------------------------------------------------- 1 | package io.metaparticle.containerlib.elector.examples; 2 | 3 | import io.metaparticle.sync.Lock; 4 | 5 | public class Main { 6 | public static void main(String[] args) throws InterruptedException { 7 | Lock l; 8 | if (args.length > 0) { 9 | l = new Lock("test", args[0]); 10 | } else { 11 | l = new Lock("test"); 12 | } 13 | System.out.println("Locking."); 14 | l.lock(); 15 | System.out.println("Sleeping."); 16 | Thread.sleep(60 * 1000); 17 | System.out.println("Unlocking."); 18 | l.unlock(); 19 | } 20 | } -------------------------------------------------------------------------------- /javascript/README.md: -------------------------------------------------------------------------------- 1 | # Examples MetaParticle Sync library for Javascript 2 | 3 | Metaparticle/Sync for Javascript is a library that implements distributed synchronization 4 | for cloud-native applications using a container side-car and Kubernetes primitives. 5 | 6 | Metaparticle/Sync for Javascript can be used for [locking](#locking-example) or for 7 | [leader election](#election-example) 8 | 9 | ## Adding the Library 10 | To add the `@metaparticle/sync` library to your code you need to do two things: 11 | 12 | * Import the library, this is commonly done with: `var mp = require('@metaparticle/sync');` 13 | * Run the `elector` side-car container. This is typically done via a Kubernetes Deployment (see examples below) 14 | 15 | ## Locking Example 16 | The simplest usage is to deploy mutual exclusion locks between different distributed components 17 | 18 | ### Code 19 | Here's the code for a simple locking example, that locks a lock named `test` and holds it for 45 seconds. 20 | 21 | ```javascript 22 | // Import the library 23 | var mp = require('@metaparticle/sync'); 24 | 25 | // Create a new lock. 26 | var lock = new mp.Lock( 27 | // The name of the lock. 28 | 'test', 29 | // This handler is called when the lock is acquired. 30 | () => { 31 | console.log('I have the lock!'); 32 | console.log('Holding the lock for 45 seconds'); 33 | setTimeout(() => { 34 | // Unlock after 45 seconds. 35 | lock.unlock(); 36 | console.log('Unlocked'); 37 | }, 45 * 1000); 38 | }, 39 | // [optional] this handler is called when the lock is lost 40 | () => { 41 | console.log('I lost the lock!'); 42 | }); 43 | 44 | // Kick off the lock, eventually this will call the callbacks above. 45 | console.log('Attempting to lock'); 46 | lock.lock(); 47 | ``` 48 | 49 | You'll notice that a lock is made up of three things: 50 | * A name (this should be unique for a cluster) 51 | * A callback function to be called when the lock is acquired. 52 | * An optional callback function to be called when the lock is lost. If this is not supplied, the program will forcibly exit in the (unlikely) case that a lock is lost. 53 | 54 | Simply creating a lock doesn't cause mutual exclusion. You also need to call `lock.lock()`. When 55 | you are done, you call `lock.unlock()` to release the lock. Locks have a TTL (time to live) so 56 | in the event of a failure, the lock will also be eventually lost. 57 | 58 | ### Deploying 59 | To deploy code using the `@metaparticle/sync` package, you need to also include a side-car that 60 | does the heavy lifting for the lock. Your code and the sidecar should both be package as containers 61 | and then deployed as a Kubernetes Pod. 62 | 63 | Here is an example Kubernetes deployment: 64 | 65 | ```yaml 66 | apiVersion: extensions/v1beta1 67 | kind: Deployment 68 | metadata: 69 | labels: 70 | run: lock-js 71 | name: lock-js 72 | namespace: default 73 | spec: 74 | replicas: 2 75 | selector: 76 | matchLabels: 77 | run: lock-js 78 | template: 79 | metadata: 80 | labels: 81 | run: lock-js 82 | spec: 83 | containers: 84 | - image: brendanburns/elector 85 | name: elector 86 | - image: brendanburns/sync-js 87 | name: example 88 | ``` 89 | 90 | You can create this with `kubectl create -f lock-deploy.yaml` which will create two different Pods, both of which are trying to obtain a lock named `test`. 91 | 92 | ## Election Example 93 | An extension of locking is _leader election_ where a leader is chosen from a group of replicas. 94 | This leader remains the leader for as long as it is healthy. If the leader ever fails, a new 95 | leader is chosen. This is an extremely useful pattern for implementing a variety of distributed systems. Generally leader election is performed for a named _shard_ which represents some piece 96 | of data to be owned/maintained by the leader. 97 | 98 | ### Code 99 | Implementing leader election in `@metaparticle/sync` is simple, here is code that performs 100 | leader election for a shard named `test`. 101 | 102 | ```javascript 103 | var mp = require('@metaparticle/sync'); 104 | 105 | var election = new mp.Election( 106 | // Name of the election shard 107 | 'test', 108 | // Event handler, called when a program becomes the leader. 109 | () => { 110 | console.log('I am the leader'); 111 | }, 112 | // Event handler, called when a program that was leader is no longer leader. 113 | () => { 114 | console.log('I lost the leader'); 115 | }); 116 | 117 | election.run(); 118 | ``` 119 | 120 | ### Deploying leader election 121 | As with locking, you need to deploy the elector side-car to take advantage of `@metaparticle/sync` elections. Here's an example Kubernetes Deployment which deploys three leader replicas: 122 | 123 | ```yaml 124 | apiVersion: extensions/v1beta1 125 | kind: Deployment 126 | metadata: 127 | labels: 128 | run: elector-js 129 | name: elector-js 130 | namespace: default 131 | spec: 132 | replicas: 3 133 | selector: 134 | matchLabels: 135 | run: elector-js 136 | template: 137 | metadata: 138 | labels: 139 | run: elector-js 140 | spec: 141 | containers: 142 | - image: brendanburns/elector 143 | imagePullPolicy: Always 144 | name: elector 145 | resources: {} 146 | # Replace the container below with your container. 147 | - image: brendanburns/sync-js 148 | name: example 149 | command: 150 | - node 151 | - elector.js 152 | ``` 153 | 154 | ## Technical Details 155 | If you are interested in the technical details of how this all works, please see the [overview](../overview.md). -------------------------------------------------------------------------------- /javascript/examples/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.7-alpine 2 | 3 | COPY package.json package.json 4 | RUN npm install 5 | 6 | COPY lock.js lock.js 7 | COPY elector.js elector.js 8 | 9 | CMD node lock.js 10 | -------------------------------------------------------------------------------- /javascript/examples/elector-deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: elector-js 6 | name: elector-js 7 | namespace: default 8 | spec: 9 | replicas: 2 10 | selector: 11 | matchLabels: 12 | run: elector-js 13 | template: 14 | metadata: 15 | creationTimestamp: null 16 | labels: 17 | run: elector-js 18 | spec: 19 | containers: 20 | - image: brendanburns/elector 21 | imagePullPolicy: Always 22 | name: elector 23 | resources: {} 24 | - image: brendanburns/sync-js 25 | name: example 26 | command: 27 | - node 28 | - elector.js 29 | dnsPolicy: ClusterFirst 30 | restartPolicy: Always 31 | schedulerName: default-scheduler 32 | -------------------------------------------------------------------------------- /javascript/examples/elector.js: -------------------------------------------------------------------------------- 1 | var mp = require('@metaparticle/sync'); 2 | 3 | var election = new mp.Election( 4 | 'test', 5 | () => { 6 | console.log('I am the master'); 7 | }, 8 | () => { 9 | console.log('I lost the master'); 10 | }); 11 | election.run(); 12 | -------------------------------------------------------------------------------- /javascript/examples/lock-deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: lock-js 6 | name: lock-js 7 | namespace: default 8 | spec: 9 | replicas: 2 10 | selector: 11 | matchLabels: 12 | run: lock-js 13 | template: 14 | metadata: 15 | creationTimestamp: null 16 | labels: 17 | run: lock-js 18 | spec: 19 | containers: 20 | - image: brendanburns/elector 21 | imagePullPolicy: Always 22 | name: elector 23 | resources: {} 24 | - image: brendanburns/sync-js 25 | name: example 26 | dnsPolicy: ClusterFirst 27 | restartPolicy: Always 28 | schedulerName: default-scheduler 29 | -------------------------------------------------------------------------------- /javascript/examples/lock.js: -------------------------------------------------------------------------------- 1 | var mp = require('@metaparticle/sync'); 2 | 3 | var lock = new mp.Lock('test', 4 | () => { 5 | console.log('I have the lock!'); 6 | console.log('Holding the lock for 45 seconds'); 7 | setTimeout(() => { 8 | lock.unlock(); 9 | console.log('Unlocked'); 10 | }, 45 * 1000); 11 | }); 12 | 13 | if (process.argv.length > 2) { 14 | lock.baseUrl = process.argv[2]; 15 | } 16 | 17 | console.log('Locking'); 18 | lock.lock(); 19 | -------------------------------------------------------------------------------- /javascript/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metaparticle/sync-example", 3 | "version": "0.0.1", 4 | "description": "Example of using library for distributed synchronization", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/metaparticle-io/sync.git" 8 | }, 9 | "files": [ 10 | "*.js" 11 | ], 12 | "main": "lock.js", 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "start": "node lock.js" 16 | }, 17 | "author": "Brendan Burns", 18 | "license": "MIT", 19 | "dependencies": { 20 | "@metaparticle/sync": "0.0.4" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/metaparticle-io/sync/issues" 24 | }, 25 | "homepage": "https://github.com/metaparticle-io/sync/javascript", 26 | "keywords": [ 27 | "cloud", 28 | "containers" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /javascript/lib/README.md: -------------------------------------------------------------------------------- 1 | # Metaparticle/Sync for Javascript 2 | 3 | This is the implementation of `@metapartile/sync`. For examples 4 | of how to use this library, see the [examples](../examples/README.md). 5 | -------------------------------------------------------------------------------- /javascript/lib/election.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var lock = require('./lock.js'); 3 | module.exports.Election = class Election { 4 | constructor(name, masterFn, lostMasterFn) { 5 | this.name = name; 6 | this.masterFn = masterFn; 7 | this.lostMasterFn = lostMasterFn; 8 | this.lock = new lock.Lock(name, this.lockAcquired.bind(this), this.lockLost.bind(this)); 9 | } 10 | 11 | lockAcquired() { 12 | setTimeout(this.masterFn, 0); 13 | } 14 | 15 | lockLost() { 16 | setTimeout(this.lostMasterFn, 0); 17 | } 18 | 19 | run() { 20 | this.lock.lock(); 21 | } 22 | 23 | shutdown() { 24 | this.lock.unlock(); 25 | } 26 | }; 27 | })(); -------------------------------------------------------------------------------- /javascript/lib/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var lock = require('./lock.js'); 3 | module.exports.Lock = lock.Lock; 4 | module.exports.debug = lock.debug; 5 | 6 | var election = require('./election.js'); 7 | module.exports.Election = election.Election; 8 | })(); -------------------------------------------------------------------------------- /javascript/lib/lock.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var request = require('request'); 3 | var log = require('loglevel'); 4 | 5 | module.exports.debug = function() { 6 | log.setLevel("debug"); 7 | } 8 | 9 | module.exports.Lock = class Lock { 10 | constructor(name, lockAcquiredFn, lockLostFn) { 11 | this.name = name; 12 | this.baseUrl = 'http://localhost:13131'; 13 | this.locked = false; 14 | this.lockAcquiredFn = lockAcquiredFn; 15 | this.lockLostFn = lockLostFn; 16 | } 17 | 18 | lock() { 19 | if (this.locked) { 20 | throw new Error('Locks are not reentrant'); 21 | } 22 | this.locked = true; 23 | this.lockInternal(); 24 | } 25 | 26 | lockInternal() { 27 | request(this.baseUrl + '/locks/' + this.name, 28 | (error, response) => { 29 | log.debug('error:', error); 30 | log.debug('statusCode:', response && response.statusCode); 31 | if (error) { 32 | if (error.code == 'ECONNREFUSED') { 33 | log.error('Could not connect to ' + this.baseUrl + ' is the elector sidecar running?'); 34 | } else { 35 | log.error('Unexpected error: ' + error); 36 | } 37 | process.exit(1); 38 | } 39 | var code = response.statusCode; 40 | if (code == 404 || code == 200) { 41 | this.updateLock(false); 42 | return; 43 | } 44 | if (code != 200) { 45 | // console.log('waiting for lock'); 46 | setTimeout(this.lockInternal.bind(this), 10 * 1000); 47 | } 48 | }); 49 | } 50 | 51 | updateLock(lockHeld) { 52 | request.put(this.baseUrl + '/locks/' + this.name, 53 | (error, response) => { 54 | log.debug('error:', error); 55 | log.debug('statusCode:', response && response.statusCode); 56 | var code = response.statusCode; 57 | if (code == 200) { 58 | if (!lockHeld) { 59 | this.lockAcquiredFn(); 60 | } 61 | setTimeout(this.holdLock.bind(this), 10 * 1000); 62 | } else { 63 | if (lockHeld) { 64 | log.warn('Unexpected code: ' + code); 65 | if (this.lockLostFn) { 66 | this.lockLostFn(); 67 | } else { 68 | process.exit(); 69 | } 70 | } else { 71 | // console.log('waiting for lock'); 72 | setTimeout(this.lockInternal.bind(this), 10 * 1000); 73 | } 74 | } 75 | }); 76 | } 77 | 78 | holdLock() { 79 | if (!this.locked) { 80 | return; 81 | } 82 | request(this.baseUrl + '/locks/' + this.name, 83 | (error, response) => { 84 | log.debug('error:', error); 85 | log.debug('statusCode:', response && response.statusCode); 86 | var code = response.statusCode; 87 | if (code == 200) { 88 | this.updateLock(true); 89 | return; 90 | } 91 | log.warn('unexpected code getting lock: ' + code); 92 | setTimeout(this.holdLock.bind(this), 10 * 1000); 93 | }); 94 | } 95 | 96 | unlock() { 97 | if (!this.locked) { 98 | throw new Error('Not locked!'); 99 | } 100 | this.locked = false; 101 | } 102 | }; 103 | })(); -------------------------------------------------------------------------------- /javascript/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metaparticle/sync", 3 | "version": "0.0.4", 4 | "description": "Library for distributed synchronization", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/metaparticle-io/container-lib.git" 8 | }, 9 | "files": [ 10 | "*.js" 11 | ], 12 | "main": "index.js", 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "start": "node index.js" 16 | }, 17 | "author": "Brendan Burns", 18 | "license": "MIT", 19 | "dependencies": { 20 | "loglevel": "^1.5.1", 21 | "request": "^2.83.0" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/metaparticle-io/container-lib/issues" 25 | }, 26 | "homepage": "https://github.com/metaparticle-io/container-lib", 27 | "keywords": [ 28 | "cloud", 29 | "containers" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /overview.md: -------------------------------------------------------------------------------- 1 | # Metaparticle/Sync Overview 2 | 3 | Metaparticle/Sync is a cloud-native standard library for synchronization in 4 | distributed systems. It provides language idiomatic locking and election 5 | for a variety of systems in different languages on top of Kubernetes. 6 | 7 | ## Design 8 | 9 | There are two components to the Metaparticle/Sync library: 10 | * A [shared sidecar container](https://github.com/metaparticle-io/container-lib/tree/master/elector) that implements most of the locking logic 11 | * Idiomatic language bindings that use the side-car to implement cloud-native locking in a particular language. 12 | 13 | ![Diagram of Metaparticle/Sync operation](images/metaparticle-sync.png) 14 | 15 | ### Sidecar container. 16 | This sidecar container runs next to the main application 17 | code, and is identical regardless of what idiomatic language binding you are 18 | using. The role of the side-car is to do the heavy lifting for the lock 19 | protocol. 20 | 21 | When it runs, the side car container creates a new Kubernetes Custom Resource 22 | Definition type called a 'lock'. When a particular lock is created, the sidecar 23 | attempts to claim the lock by writing it's hostname and a time-to-live (TTL) 24 | into the lock object. If this succeeds the sidecar has the lock for the 25 | length of the TTL. If this fails, someone else has the lock. 26 | 27 | If the sidecar acquires the lock, it heartbeats on the lock to retain the lock. 28 | If the side car fails to acquire the lock, it attempts to re-acquire the lock 29 | after it's time to live (TTL) has expired. 30 | 31 | ### Idiomatic Language bindings 32 | The idiomatic or fluent language bindings provide a native interface for 33 | distributed locks that feels natural to developers in a particular language. 34 | These bindings all assume the presence of the locking sidecar container, and 35 | indeed are largely thin programattic wrappers on top of the HTTP api exposed 36 | by the sidecar. To ensure that the locks are not held if the language 37 | code deadlocks (for example) the language bindings are responsible for 38 | heartbeatung to the sidecar container, which in turn heart-beats to the 39 | sidecar lock container. 40 | 41 | ### Protocol 42 | The sidecar exposes a simple HTTP based protocol on `http://localhost:8080`. 43 | 44 | * A `GET` to `http://localhost:8080/locks/` returns if the lock exists. 45 | If the lock exists, a `200` is returned. Otherwise a `404` is returned. 46 | * A `PUT` to `http://localhost:8080/locks/` attempts to create the lock, or heartbeats if the lock already exists. The `PUT` returns: 47 | * `200` if the create or heartbeat succeeds 48 | * `409` if the attempt to create or heartbeat the lock conflicts with someone else obtaining the lock. 49 | 50 | The TTL for the lock is currently hard-coded at 30 seconds.If a heartbeat does not occur in that time-frame, the lock is lost. 51 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | dist/** 2 | -------------------------------------------------------------------------------- /python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python 2 | 3 | RUN pip install six metaparticle_sync --no-cache 4 | COPY examples/*.py / 5 | 6 | -------------------------------------------------------------------------------- /python/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test clean venv 2 | 3 | test: 4 | (. venv/bin/activate; \ 5 | tox; \ 6 | ) 7 | 8 | venv: 9 | virtualenv -p python3 venv 10 | venv/bin/pip install --upgrade pip 11 | venv/bin/pip install --upgrade setuptools 12 | . venv/bin/activate 13 | venv/bin/pip install -e . 14 | venv/bin/pip install --upgrade tox 15 | 16 | publish: 17 | python setup.py sdist upload -r pypi 18 | 19 | clean: 20 | rm -rf build dist *.egg-info MANIFEST 21 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Examples MetaParticle Sync library for Python 2 | 3 | Metaparticle/Sync for Python is a library that implements distributed synchronization 4 | for cloud-native applications using a container side-car and Kubernetes primitives. 5 | 6 | Metaparticle/Sync for Python can be used for [locking](#locking-example) or for 7 | [leader election](#election-example) 8 | 9 | ## Adding the Library 10 | To add the `metaparticle_sync` library to your code you need to do two things: 11 | 12 | * Import the library: `import metaparticle_sync` 13 | * Run the `elector` side-car container. This is typically done via a Kubernetes Deployment 14 | (see [examples](#deploying) below) 15 | 16 | ## Locking Example 17 | The simplest usage is to deploy mutual exclusion locks between different distributed components 18 | 19 | ### Code 20 | Here's the code for a simple locking example, that locks a lock named `test` and holds it for 45 seconds. 21 | 22 | ```python 23 | from metaparticle_sync import Lock 24 | import time 25 | 26 | l = Lock('test') 27 | l.acquire() 28 | print('I have the lock!') 29 | time.sleep(30) 30 | l.release() 31 | ``` 32 | 33 | You'll notice that a lock requires a name. This name should be unique for a cluster. 34 | 35 | Simply creating a lock doesn't cause mutual exclusion. You also need to call `lock.acquire()`. When 36 | you are done, you call `lock.release() to release the lock. Locks have a TTL (time to live) so 37 | in the event of a failure, the lock will also be eventually lost. 38 | 39 | 40 | ### Deploying 41 | To deploy code using the `metaparticle_sync` package, you need to also include a side-car that 42 | does the heavy lifting for the lock. Your code and the sidecar should both be package as containers 43 | and then deployed as a Kubernetes Pod. 44 | 45 | Here is an example Kubernetes deployment: 46 | 47 | ```yaml 48 | apiVersion: extensions/v1beta1 49 | kind: Deployment 50 | metadata: 51 | labels: 52 | run: lock-python 53 | name: lock-python 54 | namespace: default 55 | spec: 56 | replicas: 2 57 | selector: 58 | matchLabels: 59 | run: lock-python 60 | template: 61 | metadata: 62 | labels: 63 | run: lock-python 64 | spec: 65 | containers: 66 | - image: brendanburns/elector 67 | name: elector 68 | # Replace this with your image. 69 | - image: brendanburns/sync-python 70 | name: example 71 | env: 72 | - name: PYTHONUNBUFFERED 73 | value: "0" 74 | command: 75 | - python 76 | - -u 77 | - /lock.py 78 | ``` 79 | 80 | You can create this with `kubectl create -f lock-deploy.yaml` which will create two different Pods, both of which are trying to obtain a lock named `test`. 81 | 82 | ## Election Example 83 | An extension of locking is _leader election_ where a leader is chosen from a group of replicas. 84 | This leader remains the leader for as long as it is healthy. If the leader ever fails, a new 85 | leader is chosen. This is an extremely useful pattern for implementing a variety of distributed systems. Generally leader election is performed for a named _shard_ which represents some piece 86 | of data to be owned/maintained by the leader. 87 | 88 | ### Code 89 | Implementing leader election in `metaparticle_sync` is simple, here is code that performs 90 | leader election for a shard named `test`. 91 | 92 | ```python 93 | import metaparticle_sync 94 | 95 | def master_fn(): 96 | print('I am the master') 97 | 98 | def lost_master_fn(): 99 | print('I lost the master') 100 | 101 | el = metaparticle_sync.Election('test', master_fn, lost_master_fn) 102 | el.run() 103 | 104 | ``` 105 | 106 | ### Deploying leader election 107 | As with locking, you need to deploy the elector side-car to take advantage of `io.metaparticle.sync` elections. Here's an example Kubernetes Deployment which deploys three leader replicas: 108 | 109 | ```yaml 110 | apiVersion: extensions/v1beta1 111 | kind: Deployment 112 | metadata: 113 | labels: 114 | run: elector-python 115 | name: elector-python 116 | namespace: default 117 | spec: 118 | replicas: 3 119 | selector: 120 | matchLabels: 121 | run: elector-python 122 | template: 123 | metadata: 124 | labels: 125 | run: elector-python 126 | spec: 127 | containers: 128 | - image: brendanburns/elector 129 | name: elector 130 | # Replace the container below with your container. 131 | - image: brendanburns/sync-python 132 | name: example 133 | env: 134 | - name: PYTHONUNBUFFERED 135 | value: "0" 136 | command: 137 | - python 138 | - -u 139 | - /election.py 140 | ``` 141 | 142 | ## Technical Details 143 | If you are interested in the technical details of how this all works, please see the [overview](../overview.md). 144 | -------------------------------------------------------------------------------- /python/examples/election.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | import metaparticle_sync 4 | 5 | 6 | def master_fn(): 7 | print('I am the master') 8 | 9 | 10 | def lost_master_fn(): 11 | print('I lost the master') 12 | 13 | 14 | el = metaparticle_sync.Election('test', master_fn, lost_master_fn) 15 | 16 | 17 | def run(): 18 | time.sleep(30) 19 | el.shutdown() 20 | 21 | 22 | def main(): 23 | t = threading.Thread(None, target=run) 24 | t.start() 25 | el.run() 26 | 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /python/examples/election.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: elector-python 6 | name: elector-python 7 | namespace: default 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | run: elector-python 13 | template: 14 | metadata: 15 | labels: 16 | run: elector-python 17 | spec: 18 | containers: 19 | - image: brendanburns/elector 20 | name: elector 21 | # Replace the container below with your container. 22 | - image: brendanburns/sync-python 23 | name: example 24 | env: 25 | - name: PYTHONUNBUFFERED 26 | value: "0" 27 | command: 28 | - python 29 | - -u 30 | - /election.py 31 | -------------------------------------------------------------------------------- /python/examples/lock.py: -------------------------------------------------------------------------------- 1 | import time 2 | from metaparticle_sync import Lock 3 | 4 | 5 | def main(): 6 | lock = Lock('test') 7 | lock.acquire() 8 | print('I have the lock!') 9 | time.sleep(30) 10 | lock.release() 11 | 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /python/examples/lock.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | run: lock-python 6 | name: lock-python 7 | namespace: default 8 | spec: 9 | replicas: 2 10 | selector: 11 | matchLabels: 12 | run: lock-python 13 | template: 14 | metadata: 15 | labels: 16 | run: lock-python 17 | spec: 18 | containers: 19 | - image: brendanburns/elector 20 | name: elector 21 | # Replace this with your image. 22 | - image: brendanburns/sync-python 23 | name: example 24 | env: 25 | - name: PYTHONUNBUFFERED 26 | value: "0" 27 | command: 28 | - python 29 | - -u 30 | - /lock.py 31 | -------------------------------------------------------------------------------- /python/metaparticle_sync/__init__.py: -------------------------------------------------------------------------------- 1 | from metaparticle_sync.lock import Lock 2 | from metaparticle_sync.election import Election 3 | -------------------------------------------------------------------------------- /python/metaparticle_sync/election.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from lock import Lock 3 | 4 | 5 | class Election: 6 | def __init__( 7 | self, name, is_master_callback, lost_master_callback): 8 | self.lock = Lock( 9 | name, lock_callback=self._lock, lock_lost_callback=self._lost_lock) 10 | self.master_callback = is_master_callback 11 | self.lost_master_callback = lost_master_callback 12 | self.running = False 13 | self.condition = threading.Condition() 14 | 15 | def shutdown(self): 16 | self.running = False 17 | self.condition.acquire() 18 | self.condition.notify() 19 | self.condition.release() 20 | 21 | def run(self): 22 | self.running = True 23 | while self.running: 24 | self.lock.acquire() 25 | self.condition.acquire() 26 | self.condition.wait() 27 | self.condition.release() 28 | self.lock.release() 29 | 30 | def _lock(self): 31 | self.master_callback() 32 | 33 | def _lost_lock(self): 34 | self.lost_master_callback() 35 | -------------------------------------------------------------------------------- /python/metaparticle_sync/lock.py: -------------------------------------------------------------------------------- 1 | from six.moves import urllib 2 | import os 3 | import sys 4 | import threading 5 | import time 6 | 7 | 8 | class Lock: 9 | def __init__( 10 | self, name, base_uri='http://localhost:13131', 11 | lock_callback=None, lock_lost_callback=None): 12 | self.name = name 13 | self.base_uri = base_uri 14 | self.maintainer = None 15 | self.lock = threading.Lock() 16 | self.running = False 17 | self.lock_callback = lock_callback 18 | self.lock_lost_callback = lock_lost_callback 19 | 20 | def acquire(self, blocking=True): 21 | if not self.lock.acquire(blocking): 22 | return False 23 | if self.maintainer is not None: 24 | self.lock.release() 25 | raise threading.ThreadError() 26 | 27 | if not blocking: 28 | result = self._acquire_lock() 29 | self.lock.release() 30 | return result 31 | 32 | while not self._acquire_lock(): 33 | time.sleep(10) 34 | 35 | self.lock.release() 36 | return True 37 | 38 | def release(self): 39 | self.lock.acquire() 40 | if self.maintainer is None: 41 | self.lock.release() 42 | raise threading.ThreadError() 43 | 44 | self.running = False 45 | self.maintainer.join() 46 | if self.lock_lost_callback is not None: 47 | self.lock_lost_callback() 48 | self.lock.release() 49 | 50 | def _acquire_lock(self): 51 | code = -1 52 | try: 53 | code = self._get_lock() 54 | if code == 404 or code == 200: 55 | code = self._update_lock() 56 | if code == 200: 57 | self._hold_lock() 58 | if self.lock_callback is not None: 59 | self.lock_callback() 60 | return True 61 | except Exception as e: 62 | print('Unexpected error: {}'.format(sys.exc_info()[0])) 63 | raise 64 | return False 65 | 66 | def _hold_lock(self): 67 | self.running = True 68 | self.maintainer = threading.Thread(target=self._run) 69 | self.maintainer.start() 70 | 71 | def _run(self): 72 | while self.running: 73 | code = self._get_lock() 74 | if code == 200: 75 | code = self._update_lock() 76 | if code != 200: 77 | print('Unexpected status: {}'.format(code)) 78 | if self.lost_lock_callback is not None: 79 | self.lost_lock_callback() 80 | break 81 | else: 82 | os.exit(-1) 83 | time.sleep(10) 84 | self.maintainer = None 85 | 86 | def _get_lock(self): 87 | req = urllib.request.Request(self.base_uri + '/locks/' + self.name) 88 | try: 89 | res = urllib.request.urlopen(req) 90 | return res.getcode() 91 | except urllib.error.HTTPError as ex: 92 | return ex.code 93 | 94 | def _update_lock(self): 95 | req = urllib.request.Request( 96 | self.base_uri + '/locks/' + self.name, 97 | headers={'accept': 'application/json'}) 98 | req.get_method = lambda: 'PUT' 99 | try: 100 | res = urllib.request.urlopen(req) 101 | return res.getcode() 102 | except urllib.error.HTTPError as ex: 103 | return ex.code 104 | -------------------------------------------------------------------------------- /python/metaparticle_sync/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.2.0' 2 | __license__ = "MIT" 3 | __status__ = "Development" 4 | -------------------------------------------------------------------------------- /python/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import os 3 | 4 | exec(open('./metaparticle_sync/version.py').read()) 5 | 6 | setuptools.setup( 7 | name='metaparticle_sync', 8 | version=__version__, 9 | url='https://github.com/metaparticle-io/sync/tree/master/python', 10 | license=__license__, 11 | description='Easy distributed locks for your python application', 12 | author='Metaparticle Authors', 13 | packages=setuptools.find_packages(), 14 | package_data={}, 15 | include_package_data=False, 16 | zip_safe=False, 17 | install_requires=[], 18 | platforms='linux', 19 | keywords=['kubernetes', 'docker', 'container', 'metaparticle'], 20 | # latest from https://pypi.python.org/pypi?%3Aaction=list_classifiers 21 | classifiers = [ 22 | 'Development Status :: 4 - Beta', 23 | 'Environment :: Console', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: MIT License', 26 | 'Natural Language :: English', 27 | 'Operating System :: POSIX :: Linux', 28 | 'Programming Language :: Python', 29 | 'Programming Language :: Python :: 2', 30 | 'Programming Language :: Python :: 3', 31 | 'Programming Language :: Python :: Implementation :: CPython', 32 | 'Topic :: Software Development :: Libraries :: Python Modules', 33 | 'Topic :: Utilities', 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "advapi32-sys" 3 | version = "0.2.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "antidote" 12 | version = "1.0.0" 13 | source = "registry+https://github.com/rust-lang/crates.io-index" 14 | 15 | [[package]] 16 | name = "base64" 17 | version = "0.6.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | dependencies = [ 20 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "bitflags" 26 | version = "0.7.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "0.9.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "byteorder" 36 | version = "1.2.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "cc" 41 | version = "1.0.3" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "chrono" 46 | version = "0.2.25" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | dependencies = [ 49 | "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 51 | ] 52 | 53 | [[package]] 54 | name = "core-foundation" 55 | version = "0.2.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "core-foundation-sys" 64 | version = "0.2.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "crypt32-sys" 72 | version = "0.2.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "emit" 81 | version = "0.10.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | dependencies = [ 84 | "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 87 | ] 88 | 89 | [[package]] 90 | name = "foreign-types" 91 | version = "0.3.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "foreign-types-shared" 99 | version = "0.1.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "fuchsia-zircon" 104 | version = "0.2.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "fuchsia-zircon-sys" 112 | version = "0.2.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "httparse" 120 | version = "1.2.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | 123 | [[package]] 124 | name = "hyper" 125 | version = "0.10.13" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | dependencies = [ 128 | "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "hyper-native-tls" 143 | version = "0.2.4" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | dependencies = [ 146 | "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 149 | ] 150 | 151 | [[package]] 152 | name = "idna" 153 | version = "0.1.4" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | dependencies = [ 156 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 157 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 159 | ] 160 | 161 | [[package]] 162 | name = "itoa" 163 | version = "0.1.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "json" 168 | version = "0.11.12" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | 171 | [[package]] 172 | name = "kernel32-sys" 173 | version = "0.2.2" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | dependencies = [ 176 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 178 | ] 179 | 180 | [[package]] 181 | name = "language-tags" 182 | version = "0.2.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | 185 | [[package]] 186 | name = "lazy_static" 187 | version = "1.0.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | 190 | [[package]] 191 | name = "libc" 192 | version = "0.2.34" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | 195 | [[package]] 196 | name = "log" 197 | version = "0.3.8" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | 200 | [[package]] 201 | name = "matches" 202 | version = "0.1.6" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | 205 | [[package]] 206 | name = "metaparticle-sync" 207 | version = "0.1.0" 208 | dependencies = [ 209 | "emit 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 210 | "requests 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)", 211 | ] 212 | 213 | [[package]] 214 | name = "mime" 215 | version = "0.2.6" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | dependencies = [ 218 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 219 | ] 220 | 221 | [[package]] 222 | name = "native-tls" 223 | version = "0.1.4" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | dependencies = [ 226 | "openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "schannel 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "num" 235 | version = "0.1.41" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 240 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 241 | ] 242 | 243 | [[package]] 244 | name = "num-integer" 245 | version = "0.1.35" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | dependencies = [ 248 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 249 | ] 250 | 251 | [[package]] 252 | name = "num-iter" 253 | version = "0.1.34" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | dependencies = [ 256 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "num-traits" 262 | version = "0.1.41" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | 265 | [[package]] 266 | name = "num_cpus" 267 | version = "1.7.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | dependencies = [ 270 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "openssl" 275 | version = "0.9.23" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "openssl-sys 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", 283 | ] 284 | 285 | [[package]] 286 | name = "openssl-sys" 287 | version = "0.9.23" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | dependencies = [ 290 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 294 | ] 295 | 296 | [[package]] 297 | name = "percent-encoding" 298 | version = "1.0.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | 301 | [[package]] 302 | name = "pkg-config" 303 | version = "0.3.9" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | 306 | [[package]] 307 | name = "rand" 308 | version = "0.3.18" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | dependencies = [ 311 | "fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 313 | ] 314 | 315 | [[package]] 316 | name = "redox_syscall" 317 | version = "0.1.32" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | 320 | [[package]] 321 | name = "requests" 322 | version = "0.0.30" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | dependencies = [ 325 | "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "json 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)", 328 | ] 329 | 330 | [[package]] 331 | name = "safemem" 332 | version = "0.2.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | 335 | [[package]] 336 | name = "schannel" 337 | version = "0.1.9" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | dependencies = [ 340 | "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 341 | "crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 342 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 343 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 344 | "secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 347 | ] 348 | 349 | [[package]] 350 | name = "secur32-sys" 351 | version = "0.2.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | dependencies = [ 354 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 356 | ] 357 | 358 | [[package]] 359 | name = "security-framework" 360 | version = "0.1.16" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | dependencies = [ 363 | "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 364 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 365 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 366 | "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 367 | ] 368 | 369 | [[package]] 370 | name = "security-framework-sys" 371 | version = "0.1.16" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | dependencies = [ 374 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 375 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 376 | ] 377 | 378 | [[package]] 379 | name = "serde" 380 | version = "0.7.15" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | 383 | [[package]] 384 | name = "serde_json" 385 | version = "0.7.4" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | dependencies = [ 388 | "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 391 | ] 392 | 393 | [[package]] 394 | name = "tempdir" 395 | version = "0.3.5" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | dependencies = [ 398 | "rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", 399 | ] 400 | 401 | [[package]] 402 | name = "time" 403 | version = "0.1.38" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | dependencies = [ 406 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 407 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 408 | "redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 409 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 410 | ] 411 | 412 | [[package]] 413 | name = "traitobject" 414 | version = "0.1.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | 417 | [[package]] 418 | name = "typeable" 419 | version = "0.1.2" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | 422 | [[package]] 423 | name = "unicase" 424 | version = "1.4.2" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | dependencies = [ 427 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 428 | ] 429 | 430 | [[package]] 431 | name = "unicode-bidi" 432 | version = "0.3.4" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | dependencies = [ 435 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 436 | ] 437 | 438 | [[package]] 439 | name = "unicode-normalization" 440 | version = "0.1.5" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | 443 | [[package]] 444 | name = "url" 445 | version = "1.6.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | dependencies = [ 448 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 451 | ] 452 | 453 | [[package]] 454 | name = "vcpkg" 455 | version = "0.2.2" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | 458 | [[package]] 459 | name = "version_check" 460 | version = "0.1.3" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | 463 | [[package]] 464 | name = "winapi" 465 | version = "0.2.8" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | 468 | [[package]] 469 | name = "winapi-build" 470 | version = "0.1.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | 473 | [metadata] 474 | "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" 475 | "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" 476 | "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" 477 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 478 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 479 | "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" 480 | "checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" 481 | "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" 482 | "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" 483 | "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" 484 | "checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" 485 | "checksum emit 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5af792f1e2fc6df1dc9b56dea2de305e52e2cb93936f4abc384fe5e0b303012" 486 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 487 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 488 | "checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" 489 | "checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82" 490 | "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" 491 | "checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2" 492 | "checksum hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72332e4a35d3059583623b50e98e491b78f8b96c5521fcb3f428167955aa56e8" 493 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 494 | "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" 495 | "checksum json 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)" = "39ebf0fac977ee3a4a3242b6446004ff64514889e3e2730bbd4f764a67a2e483" 496 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 497 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 498 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 499 | "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" 500 | "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" 501 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 502 | "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 503 | "checksum native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04b781c9134a954c84f0594b9ab3f5606abc516030388e8511887ef4c204a1e5" 504 | "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" 505 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 506 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 507 | "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" 508 | "checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d" 509 | "checksum openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "169a4b9160baf9b9b1ab975418c673686638995ba921683a7f1e01470dcb8854" 510 | "checksum openssl-sys 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "2200ffec628e3f14c39fc0131a301db214f1a7d584e36507ee8700b0c7fb7a46" 511 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 512 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 513 | "checksum rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6475140dfd8655aeb72e1fd4b7a1cc1c202be65d71669476e392fe62532b9edd" 514 | "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0" 515 | "checksum requests 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "2e29be4b28ec447cd9f6753353a1255fdf2677a30e6c7115e73667e1eb80dbf3" 516 | "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" 517 | "checksum schannel 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "4330c2e874379fbd28fa67ba43239dbe8c7fb00662ceb1078bd37474f08bf5ce" 518 | "checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" 519 | "checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" 520 | "checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" 521 | "checksum serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0e0732aa8ec4267f61815a396a942ba3525062e3bd5520aa8419927cfc0a92" 522 | "checksum serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b22e8a0554f31cb0f501e027de07b253553b308124f61c57598b9678dba35c0b" 523 | "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" 524 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 525 | "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 526 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 527 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 528 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 529 | "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" 530 | "checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2" 531 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 532 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 533 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 534 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 535 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "metaparticle-sync" 3 | exclude = ["examples/*.rs",] 4 | version = "0.1.0" 5 | authors = ["Christopher MacGown "] 6 | 7 | [dependencies] 8 | emit = "0.10" 9 | requests = "0.0.30" 10 | -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | # Metaparticle/Sync rust library 2 | 3 | Metaparticle/Sync for rust is a library that implements distributed synchronization for 4 | cloud-native applications using a container sidecar and Kubernetes primitives. 5 | 6 | 7 | ## Quickstart 8 | 9 | Add the following to your `Cargo.toml` 10 | 11 | ``` 12 | [dependencies] 13 | metaparticle_sync = "0.1" 14 | ``` 15 | 16 | ## metaparticle_sync in action 17 | 18 | ``` 19 | #[macro_use] 20 | extern crate metaparticle_sync as sync; 21 | 22 | 23 | fn main() { 24 | let lock = lock!("some-lock"); 25 | 26 | // Attempt to lock without retrying. 27 | lock.lock(|| { 28 | // do some important work 29 | }); 30 | 31 | // Block and attempt to grab the lock up to 10 times. 32 | lock.lock_with_retry(|| { 33 | // do some important work 34 | }); 35 | 36 | // Block and attempt to grab the lock forever. 37 | lock.lock_with_retry_forever(|| { 38 | // do some important work 39 | }); 40 | 41 | // Election 42 | let election = elect!("some-election", 43 | || { 44 | // Do some work when leader. 45 | }, 46 | || { 47 | // Do some work when follower. 48 | }); 49 | election.run(); 50 | } 51 | ``` 52 | 53 | 54 | 55 | 56 | ## License 57 | 58 | Licensed under either of 59 | 60 | * Apache License, Version 2.0 61 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 62 | * MIT license 63 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 64 | 65 | at your option. 66 | 67 | ## Contribution 68 | 69 | Unless you explicitly state otherwise, any contribution intentionally submitted 70 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 71 | dual licensed as above, without any additional terms or conditions. 72 | -------------------------------------------------------------------------------- /rust/examples/election/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "antidote" 3 | version = "1.0.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "base64" 8 | version = "0.6.0" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | dependencies = [ 11 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "bitflags" 17 | version = "0.9.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "1.0.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "byteorder" 27 | version = "1.2.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "cc" 32 | version = "1.0.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "0.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "chrono" 42 | version = "0.2.25" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "core-foundation" 51 | version = "0.2.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "core-foundation-sys" 60 | version = "0.2.3" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "election" 68 | version = "0.1.0" 69 | dependencies = [ 70 | "metaparticle-sync 0.1.0", 71 | ] 72 | 73 | [[package]] 74 | name = "emit" 75 | version = "0.10.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "foreign-types" 85 | version = "0.3.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | dependencies = [ 88 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "foreign-types-shared" 93 | version = "0.1.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | 96 | [[package]] 97 | name = "fuchsia-zircon" 98 | version = "0.3.3" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "fuchsia-zircon-sys" 107 | version = "0.3.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | 110 | [[package]] 111 | name = "httparse" 112 | version = "1.2.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | 115 | [[package]] 116 | name = "hyper" 117 | version = "0.10.13" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "hyper-native-tls" 135 | version = "0.2.4" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 141 | ] 142 | 143 | [[package]] 144 | name = "idna" 145 | version = "0.1.4" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | dependencies = [ 148 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 151 | ] 152 | 153 | [[package]] 154 | name = "itoa" 155 | version = "0.1.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | 158 | [[package]] 159 | name = "json" 160 | version = "0.11.12" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "language-tags" 165 | version = "0.2.2" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | 168 | [[package]] 169 | name = "lazy_static" 170 | version = "0.2.11" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | 173 | [[package]] 174 | name = "lazy_static" 175 | version = "1.0.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | 178 | [[package]] 179 | name = "libc" 180 | version = "0.2.36" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | 183 | [[package]] 184 | name = "log" 185 | version = "0.3.9" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 189 | ] 190 | 191 | [[package]] 192 | name = "log" 193 | version = "0.4.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | dependencies = [ 196 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 197 | ] 198 | 199 | [[package]] 200 | name = "matches" 201 | version = "0.1.6" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | 204 | [[package]] 205 | name = "metaparticle-sync" 206 | version = "0.1.0" 207 | dependencies = [ 208 | "emit 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "requests 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "mime" 214 | version = "0.2.6" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "native-tls" 222 | version = "0.1.5" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 232 | ] 233 | 234 | [[package]] 235 | name = "num" 236 | version = "0.1.41" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | dependencies = [ 239 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 240 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "num-integer" 246 | version = "0.1.35" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | dependencies = [ 249 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 250 | ] 251 | 252 | [[package]] 253 | name = "num-iter" 254 | version = "0.1.34" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "num-traits" 263 | version = "0.1.41" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | 266 | [[package]] 267 | name = "num_cpus" 268 | version = "1.8.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 272 | ] 273 | 274 | [[package]] 275 | name = "openssl" 276 | version = "0.9.23" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | dependencies = [ 279 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", 284 | ] 285 | 286 | [[package]] 287 | name = "openssl-sys" 288 | version = "0.9.24" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | dependencies = [ 291 | "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 295 | ] 296 | 297 | [[package]] 298 | name = "percent-encoding" 299 | version = "1.0.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | 302 | [[package]] 303 | name = "pkg-config" 304 | version = "0.3.9" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | 307 | [[package]] 308 | name = "rand" 309 | version = "0.3.20" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "redox_syscall" 318 | version = "0.1.37" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | 321 | [[package]] 322 | name = "requests" 323 | version = "0.0.30" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | dependencies = [ 326 | "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "json 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)", 329 | ] 330 | 331 | [[package]] 332 | name = "safemem" 333 | version = "0.2.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | 336 | [[package]] 337 | name = "schannel" 338 | version = "0.1.10" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | dependencies = [ 341 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 342 | "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 343 | ] 344 | 345 | [[package]] 346 | name = "security-framework" 347 | version = "0.1.16" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | dependencies = [ 350 | "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 352 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 353 | "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 354 | ] 355 | 356 | [[package]] 357 | name = "security-framework-sys" 358 | version = "0.1.16" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | dependencies = [ 361 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 362 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 363 | ] 364 | 365 | [[package]] 366 | name = "serde" 367 | version = "0.7.15" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | 370 | [[package]] 371 | name = "serde_json" 372 | version = "0.7.4" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 378 | ] 379 | 380 | [[package]] 381 | name = "tempdir" 382 | version = "0.3.5" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | dependencies = [ 385 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 386 | ] 387 | 388 | [[package]] 389 | name = "time" 390 | version = "0.1.39" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | dependencies = [ 393 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "traitobject" 400 | version = "0.1.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | 403 | [[package]] 404 | name = "typeable" 405 | version = "0.1.2" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | 408 | [[package]] 409 | name = "unicase" 410 | version = "1.4.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 414 | ] 415 | 416 | [[package]] 417 | name = "unicode-bidi" 418 | version = "0.3.4" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | dependencies = [ 421 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 422 | ] 423 | 424 | [[package]] 425 | name = "unicode-normalization" 426 | version = "0.1.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | 429 | [[package]] 430 | name = "url" 431 | version = "1.6.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | dependencies = [ 434 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 437 | ] 438 | 439 | [[package]] 440 | name = "vcpkg" 441 | version = "0.2.2" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | 444 | [[package]] 445 | name = "version_check" 446 | version = "0.1.3" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | 449 | [[package]] 450 | name = "winapi" 451 | version = "0.3.3" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 455 | "winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 456 | ] 457 | 458 | [[package]] 459 | name = "winapi-i686-pc-windows-gnu" 460 | version = "0.3.2" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | 463 | [[package]] 464 | name = "winapi-x86_64-pc-windows-gnu" 465 | version = "0.3.2" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | 468 | [metadata] 469 | "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" 470 | "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" 471 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 472 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 473 | "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" 474 | "checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" 475 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 476 | "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" 477 | "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" 478 | "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" 479 | "checksum emit 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5af792f1e2fc6df1dc9b56dea2de305e52e2cb93936f4abc384fe5e0b303012" 480 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 481 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 482 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 483 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 484 | "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" 485 | "checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2" 486 | "checksum hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72332e4a35d3059583623b50e98e491b78f8b96c5521fcb3f428167955aa56e8" 487 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 488 | "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" 489 | "checksum json 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)" = "39ebf0fac977ee3a4a3242b6446004ff64514889e3e2730bbd4f764a67a2e483" 490 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 491 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 492 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 493 | "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" 494 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 495 | "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" 496 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 497 | "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 498 | "checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" 499 | "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" 500 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 501 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 502 | "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" 503 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 504 | "checksum openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "169a4b9160baf9b9b1ab975418c673686638995ba921683a7f1e01470dcb8854" 505 | "checksum openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "14ba54ac7d5a4eabd1d5f2c1fdeb7e7c14debfa669d94b983d01b465e767ba9e" 506 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 507 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 508 | "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" 509 | "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" 510 | "checksum requests 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "2e29be4b28ec447cd9f6753353a1255fdf2677a30e6c7115e73667e1eb80dbf3" 511 | "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" 512 | "checksum schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "acece75e0f987c48863a6c792ec8b7d6c4177d4a027f8ccc72f849794f437016" 513 | "checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" 514 | "checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" 515 | "checksum serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0e0732aa8ec4267f61815a396a942ba3525062e3bd5520aa8419927cfc0a92" 516 | "checksum serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b22e8a0554f31cb0f501e027de07b253553b308124f61c57598b9678dba35c0b" 517 | "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" 518 | "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" 519 | "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 520 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 521 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 522 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 523 | "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" 524 | "checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2" 525 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 526 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 527 | "checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693" 528 | "checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" 529 | "checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668" 530 | -------------------------------------------------------------------------------- /rust/examples/election/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "election" 3 | version = "0.1.0" 4 | authors = ["Christopher MacGown "] 5 | 6 | [dependencies] 7 | "metaparticle-sync" = { path = "../.." } 8 | 9 | -------------------------------------------------------------------------------- /rust/examples/election/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate metaparticle_sync; 3 | 4 | 5 | use std::thread::sleep; 6 | use std::time::Duration; 7 | 8 | fn main() { 9 | let election = elect!("example-election", 10 | || { 11 | println!("I am the leader!"); 12 | sleep(Duration::from_millis(250)); 13 | }, 14 | || { 15 | println!("I am the follower!"); 16 | sleep(Duration::from_millis(250)); 17 | }); 18 | election.run(); 19 | } 20 | -------------------------------------------------------------------------------- /rust/examples/lock/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "antidote" 3 | version = "1.0.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "base64" 8 | version = "0.6.0" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | dependencies = [ 11 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "bitflags" 17 | version = "0.9.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "1.0.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "byteorder" 27 | version = "1.2.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "cc" 32 | version = "1.0.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "0.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "chrono" 42 | version = "0.2.25" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "core-foundation" 51 | version = "0.2.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "core-foundation-sys" 60 | version = "0.2.3" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "emit" 68 | version = "0.10.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 74 | ] 75 | 76 | [[package]] 77 | name = "foreign-types" 78 | version = "0.3.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | dependencies = [ 81 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 82 | ] 83 | 84 | [[package]] 85 | name = "foreign-types-shared" 86 | version = "0.1.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | 89 | [[package]] 90 | name = "fuchsia-zircon" 91 | version = "0.3.3" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "fuchsia-zircon-sys" 100 | version = "0.3.3" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | 103 | [[package]] 104 | name = "httparse" 105 | version = "1.2.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [[package]] 109 | name = "hyper" 110 | version = "0.10.13" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | dependencies = [ 113 | "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 124 | ] 125 | 126 | [[package]] 127 | name = "hyper-native-tls" 128 | version = "0.2.4" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | dependencies = [ 131 | "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 134 | ] 135 | 136 | [[package]] 137 | name = "idna" 138 | version = "0.1.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | dependencies = [ 141 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 144 | ] 145 | 146 | [[package]] 147 | name = "itoa" 148 | version = "0.1.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | 151 | [[package]] 152 | name = "json" 153 | version = "0.11.12" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | 156 | [[package]] 157 | name = "language-tags" 158 | version = "0.2.2" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | 161 | [[package]] 162 | name = "lazy_static" 163 | version = "0.2.11" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "lazy_static" 168 | version = "1.0.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | 171 | [[package]] 172 | name = "libc" 173 | version = "0.2.36" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | 176 | [[package]] 177 | name = "lock" 178 | version = "0.1.0" 179 | dependencies = [ 180 | "metaparticle-sync 0.1.0", 181 | ] 182 | 183 | [[package]] 184 | name = "log" 185 | version = "0.3.9" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 189 | ] 190 | 191 | [[package]] 192 | name = "log" 193 | version = "0.4.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | dependencies = [ 196 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 197 | ] 198 | 199 | [[package]] 200 | name = "matches" 201 | version = "0.1.6" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | 204 | [[package]] 205 | name = "metaparticle-sync" 206 | version = "0.1.0" 207 | dependencies = [ 208 | "emit 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "requests 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "mime" 214 | version = "0.2.6" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "native-tls" 222 | version = "0.1.5" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 232 | ] 233 | 234 | [[package]] 235 | name = "num" 236 | version = "0.1.41" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | dependencies = [ 239 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 240 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "num-integer" 246 | version = "0.1.35" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | dependencies = [ 249 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 250 | ] 251 | 252 | [[package]] 253 | name = "num-iter" 254 | version = "0.1.34" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "num-traits" 263 | version = "0.1.41" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | 266 | [[package]] 267 | name = "num_cpus" 268 | version = "1.8.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 272 | ] 273 | 274 | [[package]] 275 | name = "openssl" 276 | version = "0.9.23" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | dependencies = [ 279 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", 284 | ] 285 | 286 | [[package]] 287 | name = "openssl-sys" 288 | version = "0.9.24" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | dependencies = [ 291 | "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 295 | ] 296 | 297 | [[package]] 298 | name = "percent-encoding" 299 | version = "1.0.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | 302 | [[package]] 303 | name = "pkg-config" 304 | version = "0.3.9" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | 307 | [[package]] 308 | name = "rand" 309 | version = "0.3.20" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "redox_syscall" 318 | version = "0.1.37" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | 321 | [[package]] 322 | name = "requests" 323 | version = "0.0.30" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | dependencies = [ 326 | "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "json 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)", 329 | ] 330 | 331 | [[package]] 332 | name = "safemem" 333 | version = "0.2.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | 336 | [[package]] 337 | name = "schannel" 338 | version = "0.1.10" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | dependencies = [ 341 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 342 | "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 343 | ] 344 | 345 | [[package]] 346 | name = "security-framework" 347 | version = "0.1.16" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | dependencies = [ 350 | "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 352 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 353 | "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 354 | ] 355 | 356 | [[package]] 357 | name = "security-framework-sys" 358 | version = "0.1.16" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | dependencies = [ 361 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 362 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 363 | ] 364 | 365 | [[package]] 366 | name = "serde" 367 | version = "0.7.15" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | 370 | [[package]] 371 | name = "serde_json" 372 | version = "0.7.4" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", 378 | ] 379 | 380 | [[package]] 381 | name = "tempdir" 382 | version = "0.3.5" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | dependencies = [ 385 | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 386 | ] 387 | 388 | [[package]] 389 | name = "time" 390 | version = "0.1.39" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | dependencies = [ 393 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "traitobject" 400 | version = "0.1.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | 403 | [[package]] 404 | name = "typeable" 405 | version = "0.1.2" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | 408 | [[package]] 409 | name = "unicase" 410 | version = "1.4.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 414 | ] 415 | 416 | [[package]] 417 | name = "unicode-bidi" 418 | version = "0.3.4" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | dependencies = [ 421 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 422 | ] 423 | 424 | [[package]] 425 | name = "unicode-normalization" 426 | version = "0.1.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | 429 | [[package]] 430 | name = "url" 431 | version = "1.6.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | dependencies = [ 434 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 437 | ] 438 | 439 | [[package]] 440 | name = "vcpkg" 441 | version = "0.2.2" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | 444 | [[package]] 445 | name = "version_check" 446 | version = "0.1.3" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | 449 | [[package]] 450 | name = "winapi" 451 | version = "0.3.3" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 455 | "winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 456 | ] 457 | 458 | [[package]] 459 | name = "winapi-i686-pc-windows-gnu" 460 | version = "0.3.2" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | 463 | [[package]] 464 | name = "winapi-x86_64-pc-windows-gnu" 465 | version = "0.3.2" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | 468 | [metadata] 469 | "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" 470 | "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" 471 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 472 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 473 | "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" 474 | "checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" 475 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 476 | "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" 477 | "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" 478 | "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" 479 | "checksum emit 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5af792f1e2fc6df1dc9b56dea2de305e52e2cb93936f4abc384fe5e0b303012" 480 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 481 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 482 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 483 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 484 | "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" 485 | "checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2" 486 | "checksum hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72332e4a35d3059583623b50e98e491b78f8b96c5521fcb3f428167955aa56e8" 487 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 488 | "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" 489 | "checksum json 0.11.12 (registry+https://github.com/rust-lang/crates.io-index)" = "39ebf0fac977ee3a4a3242b6446004ff64514889e3e2730bbd4f764a67a2e483" 490 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 491 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 492 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 493 | "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" 494 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 495 | "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" 496 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 497 | "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 498 | "checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" 499 | "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" 500 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 501 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 502 | "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" 503 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 504 | "checksum openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "169a4b9160baf9b9b1ab975418c673686638995ba921683a7f1e01470dcb8854" 505 | "checksum openssl-sys 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "14ba54ac7d5a4eabd1d5f2c1fdeb7e7c14debfa669d94b983d01b465e767ba9e" 506 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 507 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 508 | "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" 509 | "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" 510 | "checksum requests 0.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "2e29be4b28ec447cd9f6753353a1255fdf2677a30e6c7115e73667e1eb80dbf3" 511 | "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" 512 | "checksum schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "acece75e0f987c48863a6c792ec8b7d6c4177d4a027f8ccc72f849794f437016" 513 | "checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" 514 | "checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" 515 | "checksum serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0e0732aa8ec4267f61815a396a942ba3525062e3bd5520aa8419927cfc0a92" 516 | "checksum serde_json 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b22e8a0554f31cb0f501e027de07b253553b308124f61c57598b9678dba35c0b" 517 | "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" 518 | "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" 519 | "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 520 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 521 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 522 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 523 | "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" 524 | "checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2" 525 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 526 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 527 | "checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693" 528 | "checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" 529 | "checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668" 530 | -------------------------------------------------------------------------------- /rust/examples/lock/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lock" 3 | version = "0.1.0" 4 | authors = ["Christopher MacGown "] 5 | 6 | [dependencies] 7 | "metaparticle-sync" = { path = "../.." } 8 | -------------------------------------------------------------------------------- /rust/examples/lock/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate metaparticle_sync; 3 | 4 | 5 | use std::thread::sleep; 6 | use std::time::Duration; 7 | 8 | fn main() { 9 | // This will retry ten times before failing. 10 | lock!("example-lock", 11 | || { 12 | println!("Just an example lock"); 13 | sleep(Duration::from_millis(250)) 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /rust/src/election.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Christopher MacGown 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | // 9 | 10 | use std::sync::Arc; 11 | use std::sync::atomic::{AtomicBool, Ordering}; 12 | 13 | use lock; 14 | 15 | 16 | /// Helper macro for invoking election synchronization 17 | /// 18 | /// # Example 19 | /// 20 | /// ``` 21 | /// #[macro_use] 22 | /// extern crate metaparticle_sync; 23 | /// 24 | /// #[derive(Copy,Clone)] 25 | /// struct DatabaseMigrator; 26 | /// impl DatabaseMigrator { 27 | /// pub fn new() -> Self { 28 | /// DatabaseMigrator{} 29 | /// } 30 | /// 31 | /// pub fn migrate(&self) { 32 | /// // ... long-lasting work 33 | /// } 34 | /// 35 | /// pub fn watch(&self) { 36 | /// // ... keep an eye on it. 37 | /// } 38 | /// } 39 | /// 40 | /// fn main() { 41 | /// let migrator = DatabaseMigrator::new(); 42 | /// elect!("database-migration", 43 | /// || migrator.migrate(), // This closure is invoked when leader 44 | /// || migrator.watch()); // This closure is invoked when follower 45 | /// } 46 | /// ``` 47 | /// 48 | #[macro_export] 49 | macro_rules! elect { 50 | ($name: tt) => ( $crate::Election::new($name, 51 | $crate::DEFAULT_BASE_URI, 52 | Box::new(|| {}), 53 | Box::new(|| {})) 54 | ); 55 | ($name: tt, $leaderfn:expr, $followerfn:expr) => {{ 56 | let mut election = elect!($name); 57 | election.add_handler($crate::Handler::Leader , Box::new($leaderfn)); 58 | election.add_handler($crate::Handler::Follower, Box::new($followerfn)); 59 | election 60 | }}; 61 | ($name: tt, $client:tt) => {{ 62 | use std::sync::Arc; 63 | 64 | let mut election = elect!($name); 65 | election.lock.client = Arc::new($client); 66 | election 67 | }}; 68 | ($name: tt, $client:tt, $leaderfn:expr, $followerfn:expr) => {{ 69 | use std::sync::Arc; 70 | 71 | let mut election = elect!($name); 72 | election.add_handler($crate::Handler::Leader , Box::new($leaderfn)); 73 | election.add_handler($crate::Handler::Follower, Box::new($followerfn)); 74 | election.lock.client = Arc::new($client); 75 | election 76 | }}; 77 | } 78 | 79 | 80 | /// Metaparticle.io Election primitive. 81 | /// 82 | /// As in the `elect!` macro example, you can create an election directly using 83 | /// the `Election` primitive. It's preferred to use the macro, however. 84 | /// 85 | /// # Example 86 | /// 87 | /// ``` 88 | /// extern crate metaparticle_sync as sync; 89 | /// 90 | /// #[derive(Copy,Clone)] 91 | /// struct DatabaseMigrator; 92 | /// impl DatabaseMigrator { 93 | /// pub fn new() -> Self { 94 | /// DatabaseMigrator{} 95 | /// } 96 | /// 97 | /// pub fn migrate(&self) { 98 | /// // ... long-lasting work 99 | /// } 100 | /// 101 | /// pub fn watch(&self) { 102 | /// // ... keep an eye on it. 103 | /// } 104 | /// } 105 | /// 106 | /// fn main() { 107 | /// let migrator = DatabaseMigrator::new(); 108 | /// 109 | /// // Election setup. 110 | /// let mut elector = sync::Election::new("database-migration", 111 | /// sync::DEFAULT_BASE_URI, 112 | /// Box::new(||{}), 113 | /// Box::new(||{})); 114 | /// 115 | /// elector.add_handler(sync::Handler::Leader , Box::new(|| migrator.migrate())); 116 | /// elector.add_handler(sync::Handler::Follower, Box::new(|| migrator.watch())); 117 | /// 118 | /// // ... Do other stuff 119 | /// 120 | /// elector.run(); 121 | /// 122 | /// } 123 | /// ``` 124 | 125 | #[derive(Clone)] 126 | pub struct Election<'a> { 127 | lock: lock::Lock, 128 | running: Arc, 129 | 130 | leader_fn: Arc () + Send + Sync + 'a>>, 131 | follower_fn: Arc () + Send + Sync + 'a>>, 132 | } 133 | 134 | pub enum Handler { 135 | Leader, 136 | Follower, 137 | } 138 | 139 | impl<'a> Election<'a> { 140 | pub fn new>(name: T, base_uri: T, 141 | leader_fn: Box () + Send + Sync + 'a>, 142 | follower_fn: Box () + Send + Sync + 'a>) -> Self { 143 | Election{ 144 | lock: lock::Lock::new(name, base_uri, 10), 145 | running: Arc::new(AtomicBool::new(false)), 146 | leader_fn: Arc::new(leader_fn), 147 | follower_fn: Arc::new(follower_fn), 148 | } 149 | } 150 | 151 | pub fn is_running(&self) -> bool { 152 | self.running.load(Ordering::Relaxed) 153 | } 154 | 155 | pub fn run(&self) { 156 | let leader_fn = self.leader_fn.clone(); 157 | let follower_fn = self.follower_fn.clone(); 158 | self.running.store(true, Ordering::Relaxed); 159 | while self.is_running() { 160 | self.lock.lock(|| leader_fn()); 161 | 162 | follower_fn(); 163 | break; 164 | } 165 | } 166 | 167 | pub fn shutdown(&self) { 168 | self.running.store(false, Ordering::Relaxed); 169 | } 170 | 171 | pub fn add_handler(&mut self, typ: Handler, handler: Box () + Send + Sync + 'a>) { 172 | match typ { 173 | Handler::Leader => self.leader_fn = Arc::new(handler), 174 | Handler::Follower => self.follower_fn = Arc::new(handler), 175 | 176 | }; 177 | } 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use std::collections::HashMap; 183 | use std::sync::{Arc, Mutex}; 184 | use std::thread::{self, sleep}; 185 | use std::time::{Duration, Instant}; 186 | 187 | use requests::{StatusCode, Error}; 188 | 189 | use lock::MockableLockClient; 190 | 191 | use election; 192 | 193 | #[derive(Debug,Clone)] 194 | struct MockLock((String, Instant)); 195 | 196 | #[derive(Debug,Clone)] 197 | struct MockLockServer(Arc>>); 198 | impl MockLockServer { 199 | fn new() -> Self { 200 | MockLockServer(Arc::new(Mutex::new(HashMap::new()))) 201 | } 202 | } 203 | 204 | #[derive(Debug,Clone)] 205 | struct MockClient((String, MockLockServer)); 206 | 207 | impl PartialEq for MockClient { 208 | fn eq(&self, rhs: &Self) -> bool { 209 | (self.0).0 == (rhs.0).0 210 | } 211 | } 212 | 213 | impl MockClient { 214 | fn new>(client: T, server: MockLockServer) -> Self { 215 | MockClient((client.into(), server)) 216 | } 217 | } 218 | 219 | impl MockableLockClient for MockClient { 220 | fn get_lock(&self, lock: &str) -> Result { 221 | let &MockClient((_, ref mutex)) = self; 222 | let locks = mutex.0.lock().unwrap(); 223 | 224 | if locks.contains_key(lock) { 225 | return Ok(StatusCode::Ok); 226 | } 227 | Ok(StatusCode::NotFound) 228 | } 229 | 230 | fn put_lock(&self, lock: &str) -> Result { 231 | let &MockClient((ref client, ref mutex)) = self; 232 | let mut locks = mutex.0.lock().unwrap(); 233 | 234 | match locks.get(lock) { 235 | Some(mocklock) => { 236 | let &MockLock((ref lockclient, ref timeout)) = mocklock; 237 | let elapsed = Instant::now().duration_since(*timeout); 238 | 239 | if lockclient != &*client && elapsed < Duration::new(1, 0) { 240 | return Ok(StatusCode::Conflict) 241 | } 242 | }, 243 | _ => {}, 244 | } 245 | locks.insert(lock.to_string(), MockLock((client.to_string(), Instant::now()))); 246 | Ok(StatusCode::Ok) 247 | } 248 | } 249 | 250 | #[test] 251 | fn test_run_election_with_add_handler() { 252 | let server = MockLockServer::new(); 253 | let client0 = MockClient::new("client0", server.clone()); 254 | let client1 = MockClient::new("client1", server.clone()); 255 | let client2 = MockClient::new("client2", server.clone()); 256 | 257 | let expected = client0.clone(); 258 | 259 | let leading: Arc>> = Arc::new(Mutex::new(None)); 260 | let mut mutexes: Vec>>> = (0..3).map(|_| leading.clone()) 261 | .collect(); 262 | 263 | let clients = vec![client0, client1, client2]; 264 | 265 | for client in clients.into_iter() { 266 | let mutex = mutexes.remove(0); 267 | let clone = client.clone(); 268 | 269 | let mut elector = elect!("fake-lock", clone); 270 | elector.add_handler(election::Handler::Leader, 271 | Box::new(move || { 272 | *(mutex.lock()).unwrap() = Some(client.clone()); 273 | sleep(Duration::from_millis(250)); 274 | })); 275 | elector.add_handler(election::Handler::Follower, 276 | Box::new(move || { 277 | sleep(Duration::from_millis(250)); 278 | })); 279 | 280 | thread::spawn(move || { elector.run() }); 281 | } 282 | 283 | sleep(Duration::from_millis(500)); 284 | assert_eq!(*leading.lock().unwrap(), Some(expected)); 285 | } 286 | 287 | #[test] 288 | fn test_run_election() { 289 | let server = MockLockServer::new(); 290 | let client0 = MockClient::new("client0", server.clone()); 291 | let client1 = MockClient::new("client1", server.clone()); 292 | let client2 = MockClient::new("client2", server.clone()); 293 | 294 | let expected = client0.clone(); 295 | 296 | let leading: Arc>> = Arc::new(Mutex::new(None)); 297 | let mut mutexes: Vec>>> = (0..3).map(|_| leading.clone()) 298 | .collect(); 299 | 300 | let clients = vec![client0, client1, client2]; 301 | 302 | for client in clients.into_iter() { 303 | let mutex = mutexes.remove(0); 304 | let clone = client.clone(); 305 | let elector = elect!("fake-lock", clone, 306 | move || { 307 | *(mutex.lock()).unwrap() = Some(client.clone()); 308 | sleep(Duration::from_millis(250)); 309 | }, 310 | move || { 311 | sleep(Duration::from_millis(250)); 312 | }); 313 | thread::spawn(move || { elector.run() }); 314 | } 315 | 316 | sleep(Duration::from_millis(500)); 317 | assert_eq!(*leading.lock().unwrap(), Some(expected)); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Christopher MacGown 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | // 9 | 10 | //! # Metaparticle/Sync 11 | //! 12 | //! `metaparticle_sync` is a library for synchronization across multiple 13 | //! containers running on different machines. 14 | //! 15 | //! 16 | //! 17 | 18 | 19 | #[macro_use] 20 | extern crate emit; 21 | extern crate requests; 22 | 23 | mod election; 24 | mod lock; 25 | 26 | pub use self::election::{Election, Handler}; 27 | pub use self::lock::{Lock, DEFAULT_BASE_URI}; 28 | -------------------------------------------------------------------------------- /rust/src/lock.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Christopher MacGown 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | // 9 | 10 | use std::fmt::Debug; 11 | use std::sync::{Arc, Mutex, Condvar}; 12 | use std::sync::atomic::{AtomicBool, Ordering}; 13 | use std::thread::{sleep, spawn, JoinHandle}; 14 | use std::time::{Duration}; 15 | 16 | use requests::{get, put, Error, StatusCode}; 17 | 18 | 19 | pub const DEFAULT_BASE_URI: &'static str = "http://localhost:8080"; 20 | 21 | 22 | 23 | /// Helper macro for invoking lock synchronization 24 | /// 25 | /// # Example 26 | /// 27 | /// ``` 28 | /// 29 | /// #[ macro_use ] 30 | /// extern crate metaparticle_sync as sync; 31 | /// 32 | /// 33 | /// fn main() { 34 | /// let lock = lock!("some-lock"); 35 | /// lock.lock(|| { 36 | /// // .. do some work 37 | /// }); 38 | /// 39 | /// lock!("some-other-lock", || { 40 | /// // .. do some work 41 | /// }); 42 | /// } 43 | /// ``` 44 | /// 45 | #[macro_export] 46 | macro_rules! lock { 47 | ($name:tt) => ( $crate::Lock::new($name, $crate::DEFAULT_BASE_URI, 10); ); 48 | ($name:tt, $lock_handler:expr) => {{ 49 | let lock = lock!($name); 50 | lock.lock( $lock_handler ); 51 | }} 52 | } 53 | 54 | 55 | pub trait MockableLockClient: Debug+Send+Sync { 56 | fn get_lock(&self, &str) -> Result; 57 | fn put_lock(&self, &str) -> Result; 58 | } 59 | 60 | 61 | struct Heartbeat{ 62 | running: AtomicBool, 63 | wait_interval: u64, 64 | } 65 | 66 | impl Heartbeat { 67 | fn new(interval: u64) -> Self { 68 | Heartbeat{ 69 | running: AtomicBool::new(false), 70 | wait_interval: interval, 71 | } 72 | } 73 | 74 | fn is_running(&self) -> bool { 75 | self.running.load(Ordering::Relaxed) 76 | } 77 | 78 | fn start(&self) { 79 | self.running.store(true, Ordering::Relaxed); 80 | } 81 | 82 | fn stop(&self) { 83 | self.running.store(false, Ordering::Relaxed); 84 | } 85 | 86 | fn beat(&self, mut block: F) 87 | where F: FnMut() -> () 88 | { 89 | self.start(); 90 | while self.is_running() { 91 | // TODO - Add exponential backoff? 92 | sleep(Duration::from_millis(self.wait_interval)); 93 | block(); 94 | } 95 | } 96 | } 97 | 98 | #[derive(Debug)] 99 | struct Client; 100 | impl Client { 101 | fn new() -> Self { 102 | Client{} 103 | } 104 | } 105 | 106 | impl MockableLockClient for Client { 107 | fn get_lock(&self, lock: &str) -> Result { 108 | match get(lock) { 109 | Ok(response) => Ok(response.status_code()), 110 | Err(error) => Err(error), 111 | } 112 | } 113 | 114 | fn put_lock(&self, lock: &str) -> Result { 115 | match put(lock) { 116 | Ok(response) => Ok(response.status_code()), 117 | Err(error) => Err(error), 118 | } 119 | } 120 | } 121 | 122 | 123 | /// Metaparticle.io Lock primitive. 124 | /// 125 | /// As in the `lock!` macro example, you can create a lock directly using the 126 | /// `Lock` primitive. In general, the 1-ary instance of the `lock!` macro is 127 | /// preferable, however. 128 | /// 129 | /// # Example (preferred) 130 | /// ``` 131 | /// #[ macro_use ] 132 | /// extern crate metaparticle_sync as sync; 133 | /// 134 | /// fn main() { 135 | /// let lock = lock!("some-held-lock"); 136 | /// 137 | /// // Attempt to lock without retrying. 138 | /// lock.lock(|| { 139 | /// // do some important work 140 | /// }); 141 | /// 142 | /// // Block and attempt to grab the lock up to 10 times. 143 | /// lock.lock_with_retry(|| { 144 | /// // do some important work 145 | /// }); 146 | /// 147 | /// // Block and attempt to grab the lock forever. 148 | /// lock.lock_with_retry_forever(|| { 149 | /// // do some important work 150 | /// }); 151 | /// } 152 | /// 153 | /// 154 | /// ``` 155 | /// 156 | /// # Example (raw primitives) 157 | /// ``` 158 | /// extern crate metaparticle_sync as sync; 159 | /// 160 | /// fn main() { 161 | /// let interval = 10; // Heartbeat interval 162 | /// let lock = sync::Lock::new("some-held-lock", sync::DEFAULT_BASE_URI, interval); 163 | /// 164 | /// // Attempt to lock without retrying. 165 | /// lock.lock(|| { 166 | /// // do some important work 167 | /// }); 168 | /// 169 | /// // Block and attempt to grab the lock up to 10 times. 170 | /// lock.lock_with_retry(|| { 171 | /// // do some important work 172 | /// }); 173 | /// 174 | /// // Block and attempt to grab the lock forever. 175 | /// lock.lock_with_retry_forever(|| { 176 | /// // do some important work 177 | /// }); 178 | /// } 179 | /// ``` 180 | /// 181 | /// 182 | #[derive(Clone)] 183 | pub struct Lock { 184 | name: String, 185 | base_uri: String, 186 | 187 | locked: Arc, 188 | pub (crate) client: Arc, 189 | heartbeat: Arc, 190 | } 191 | impl Lock { 192 | pub fn new>(name: S, base_uri: S, interval: u64) -> Self { 193 | Lock{ 194 | name: name.into(), 195 | base_uri: base_uri.into(), 196 | 197 | client: Arc::new(Client::new()), 198 | locked: Arc::new(AtomicBool::new(false)), 199 | heartbeat: Arc::new(Heartbeat::new(interval * 1000)), 200 | } 201 | } 202 | 203 | fn uri(&self) -> String { 204 | format!("{}/locks/{}", self.base_uri, self.name) 205 | } 206 | 207 | fn spin_heartbeat(&self, pair: Arc<(Mutex, Condvar)>) -> JoinHandle<()> { 208 | let uri = self.uri(); 209 | let client = self.client.clone(); 210 | let heartbeat = self.heartbeat.clone(); 211 | 212 | spawn(move || { 213 | heartbeat.beat(|| { 214 | let &(ref lock, ref condition) = &*pair; 215 | 216 | match client.get_lock(&uri) { 217 | Ok(status) => { 218 | if status == StatusCode::Ok { 219 | heartbeat.stop(); 220 | let mut available = lock.lock().unwrap(); 221 | *available = true; 222 | 223 | condition.notify_one(); 224 | } 225 | }, 226 | Err(reason) => { 227 | error!("Could not get_lock: {}", 228 | error: reason.to_string()) 229 | } 230 | } 231 | }) 232 | }) 233 | } 234 | 235 | fn hold_heartbeat(&self) -> JoinHandle<()> { 236 | let uri = self.uri(); 237 | let client = self.client.clone(); 238 | let locked = self.locked.clone(); 239 | let heartbeat = self.heartbeat.clone(); 240 | 241 | locked.store(true, Ordering::Relaxed); 242 | spawn(move || { 243 | heartbeat.beat(|| { 244 | match client.get_lock(&uri) { 245 | Ok(status) => { 246 | if status == StatusCode::Ok { 247 | // Do we need to check the result here is ok? 248 | let _ = client.put_lock(&uri); 249 | } else { 250 | heartbeat.stop(); 251 | locked.store(false, Ordering::Relaxed); 252 | } 253 | }, 254 | Err(err) => { 255 | error!("Could not get lock {}: {}", 256 | lock: uri, 257 | error: err.to_string()) 258 | } 259 | } 260 | }) 261 | }) 262 | } 263 | 264 | pub fn is_locked(&self) -> bool { 265 | self.locked.load(Ordering::Relaxed) 266 | } 267 | 268 | pub fn unlock(&self) { 269 | self.heartbeat.stop(); 270 | self.locked.store(false, Ordering::Relaxed); 271 | } 272 | 273 | pub fn lock ()>(&self, func: T){ 274 | self._lock(0, func); 275 | } 276 | 277 | pub fn lock_with_retry ()>(&self, func: T){ 278 | self._lock(10, func); // TODO - Should this be specified? 279 | } 280 | 281 | pub fn lock_with_retry_forever ()>(&self, func: T) { 282 | self._lock(-1, func); 283 | } 284 | 285 | fn _lock ()>(&self, retry: i8, func: T) { 286 | if self.is_locked() { 287 | error!("Locks are not reentrant {}", lock: self.name); 288 | } 289 | 290 | match self.client.get_lock(&self.uri()) { 291 | Ok(status) => { 292 | match status { 293 | StatusCode::Ok | StatusCode::NotFound => { 294 | match self.client.put_lock(&self.uri()) { 295 | Ok(status) => { 296 | match status { 297 | StatusCode::Ok => { 298 | let hold = self.hold_heartbeat(); 299 | 300 | func(); 301 | 302 | self.heartbeat.stop(); 303 | let _ = hold.join(); // The handle output is unimportant 304 | }, 305 | StatusCode::Conflict => { 306 | if retry == 0 { 307 | info!("Couldn't grab lock {} retry {}", lock: self.name, retry: retry); 308 | return 309 | } 310 | 311 | let pair = Arc::new((Mutex::new(false), Condvar::new())); 312 | let spin = self.spin_heartbeat(pair.clone()); 313 | 314 | let &(ref lock, ref condition) = &*pair; 315 | let mut available = lock.lock().unwrap(); 316 | loop { 317 | let r = condition.wait_timeout(available, Duration::from_millis(500)) 318 | .unwrap(); 319 | 320 | available = r.0; 321 | if *available { 322 | break 323 | } 324 | } 325 | let _ = spin.join(); 326 | 327 | let mut retry = retry; 328 | if retry != -1 { 329 | retry -= 1; 330 | } 331 | 332 | self._lock(retry, func); 333 | }, 334 | _ => unreachable!(), 335 | } 336 | }, 337 | Err(err) => { 338 | error!("Could not put lock {}: {}", 339 | lock: self.uri(), 340 | error: err.to_string()) 341 | } 342 | } 343 | }, 344 | _ => { 345 | unreachable!(); 346 | }, 347 | } 348 | }, 349 | Err(err) => { 350 | error!("Could not get lock {}: {}", 351 | lock: self.uri(), 352 | error: err.to_string()) 353 | } 354 | } 355 | } 356 | } 357 | 358 | 359 | #[cfg(test)] 360 | mod tests { 361 | extern crate requests; 362 | 363 | use std::collections::HashMap; 364 | use std::sync::{Arc, Mutex}; 365 | use std::thread::sleep; 366 | use std::time::{Duration, Instant}; 367 | 368 | use requests::{StatusCode, Error}; 369 | 370 | use lock::{Lock, MockableLockClient}; 371 | 372 | #[derive(Debug,Clone)] 373 | struct MockLock((String, Instant)); 374 | 375 | #[derive(Debug,Clone)] 376 | struct MockLockServer(Arc>>); 377 | impl MockLockServer { 378 | fn new() -> Self { 379 | MockLockServer(Arc::new(Mutex::new(HashMap::new()))) 380 | } 381 | } 382 | 383 | #[derive(Debug,Clone)] 384 | struct MockClient((String, MockLockServer)); 385 | impl MockClient { 386 | fn new>(client: T, server: MockLockServer) -> Self { 387 | MockClient((client.into(), server)) 388 | } 389 | 390 | fn new_lock>(&self, lock: T, base_uri: T) -> Lock { 391 | let mut lock = Lock::new(lock, base_uri, 1); 392 | let client = Arc::new(self.clone()); 393 | lock.client = client; 394 | lock 395 | } 396 | } 397 | 398 | impl MockableLockClient for MockClient { 399 | fn get_lock(&self, lock: &str) -> Result { 400 | let &MockClient((_, ref mutex)) = self; 401 | let locks = mutex.0.lock().unwrap(); 402 | 403 | if locks.contains_key(lock) { 404 | return Ok(StatusCode::Ok); 405 | } 406 | Ok(StatusCode::NotFound) 407 | } 408 | 409 | fn put_lock(&self, lock: &str) -> Result { 410 | let &MockClient((ref client, ref mutex)) = self; 411 | let mut locks = mutex.0.lock().unwrap(); 412 | 413 | match locks.get(lock) { 414 | Some(mocklock) => { 415 | let &MockLock((ref lockclient, ref timeout)) = mocklock; 416 | let elapsed = Instant::now().duration_since(*timeout); 417 | 418 | if lockclient != &*client && elapsed < Duration::new(1, 0) { 419 | return Ok(StatusCode::Conflict) 420 | } 421 | }, 422 | _ => {}, 423 | } 424 | locks.insert(lock.to_string(), MockLock((client.to_string(), Instant::now()))); 425 | Ok(StatusCode::Ok) 426 | } 427 | } 428 | 429 | #[test] 430 | fn test_locking_with_macros() { 431 | lock!("macro-lock", || { sleep(Duration::from_millis(250)) }) 432 | } 433 | 434 | #[test] 435 | fn test_locking_without_retrying() { 436 | let server = MockLockServer::new(); 437 | let client1 = MockClient::new("client1", server.clone()); 438 | let client2 = MockClient::new("client2", server.clone()); 439 | 440 | let lock = client1.new_lock("good", "localhost:8080"); 441 | let lock2 = client2.new_lock("good", "localhost:8080"); 442 | 443 | lock.lock(|| { 444 | println!("DOING THE WORK"); 445 | sleep(Duration::from_millis(250)); 446 | println!("DONE!"); 447 | }); 448 | lock2.lock(|| { 449 | println!("DOING THE WORK"); 450 | sleep(Duration::from_millis(250)); 451 | println!("DONE!"); 452 | }); 453 | 454 | assert_eq!(lock.is_locked(), true); 455 | assert_eq!(lock2.is_locked(), false); 456 | } 457 | 458 | #[test] 459 | fn test_locking_with_retrying() { 460 | let server = MockLockServer::new(); 461 | let client1 = MockClient::new("client1", server.clone()); 462 | let client2 = MockClient::new("client2", server.clone()); 463 | 464 | let lock = client1.new_lock("good", "localhost:8080"); 465 | let lock2 = client2.new_lock("good", "localhost:8080"); 466 | 467 | lock.lock_with_retry(|| { 468 | println!("DOING THE WORK"); 469 | sleep(Duration::from_millis(250)); 470 | println!("DONE!"); 471 | }); 472 | lock2.lock_with_retry(|| { 473 | println!("DOING THE WORK"); 474 | sleep(Duration::from_millis(250)); 475 | println!("DONE!"); 476 | }); 477 | 478 | assert_eq!(lock.is_locked(), true); 479 | assert_eq!(lock2.is_locked(), true); 480 | } 481 | } 482 | --------------------------------------------------------------------------------