├── .gitignore
├── .project
├── .travis.yml
├── Cargo.toml
├── LICENSE
├── README.md
└── src
├── lib.rs
└── test.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | pubsub
4 |
5 |
6 |
7 |
8 |
9 | com.github.rustdt.ide.core.Builder
10 |
11 |
12 |
13 |
14 |
15 | com.github.rustdt.ide.core.nature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 | sudo: required
3 | after_success: |-
4 | [ $TRAVIS_BRANCH = master ] &&
5 | [ $TRAVIS_PULL_REQUEST = false ] &&
6 | cargo doc &&
7 | echo "" > target/doc/index.html &&
8 | sudo pip install ghp-import &&
9 | ghp-import -n target/doc &&
10 | git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
11 | env:
12 | global:
13 | secure: Mu9uI+RSo+dkwY93Uqbzr/r/Kq4PfLoWnfgLFUvgHnDrCiVyCCNxwBNiMXK4eIg9ALg2Tsa5wM1T62C5nrYws352Wml+oIXBTrfr6ec7jYV+EbdEl/KryhZk1VO6X1zeKg8HfIAKqOJ7r72RZmBUhZmrpQIlBYR9ns98WJ0hEOpIwbkiy6X3hNoKA3Uh3XhV59u9IoF5Z9lImV/Mknqf9zZsmUt/A7xXZ6ZpMD81ayJu7/S4eyQOQRSM3E11VtnHyG1bjNUXFr4VCTUSfDhmSrY39B2CnZRMH9c8WIejyk8d4jCe5mvgYBsoPw/uLXR8pZN/eROkV5V6Ilew8deAPoLgHJkziStiMLPhSurysWQ3+4haWNx6qs13NU0yrDcIEO64+m+v2IQDtxWXRwEKI4V3SO3KSYVQDM0dTwVMP6NXf5pz7QpyqMGY1b2HXUTOENGWECPn1DEkJNfzgwFhgDhBtbtUdc5Y/pC5lt0SJkCJY9mvBUjyjYiiy0dymkLTgxOxYMZ6EWTfoUDpx0bLnvbVNiJGwQzE9Yh/fMwEACsGmAS6O6qEr3NHQjv64zcSjGM5DC4jV0EevaqAtNmGVmp31JX5kShsXY87mkXVCTCmKC2pldmEWvK4VzPC4n7ZB4YEx8j8/3pqknWucyrT1j6WNb2wrkGy9UXGujxMiMk=
14 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 |
3 | name = "pubsub"
4 | version = "0.2.3"
5 | authors = ["Nathan Fuchs "]
6 | license = "MIT"
7 | readme = "README.md"
8 | repository = "https://github.com/fuchsnj/rust_pubsub"
9 | documentation = "http://fuchsnj.github.io/rust_pubsub"
10 | description = "Local publish / subscribe"
11 | keywords = ["pubsub", "publish", "subscribe", "local"]
12 |
13 | [dependencies]
14 | threadpool = "0.1.4"
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Nathan Fuchs
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Local publish/subscribe
2 | ====
3 | [](https://travis-ci.org/fuchsnj/rust_pubsub)
4 | [](https://crates.io/crates/pubsub)
5 | [](./LICENSE)
6 | ## Documentation
7 |
8 | http://fuchsnj.github.io/rust_pubsub
9 |
10 | ## Usage
11 |
12 | Add the following to your `Cargo.toml`
13 |
14 | ```toml
15 | [dependencies]
16 | pubsub = "*"
17 | ```
18 |
19 | and this to your crate root:
20 |
21 | ```rust
22 | extern crate pubsub;
23 | ```
24 |
25 | ## Quick Start
26 |
27 | ```rust
28 | extern crate pubsub;
29 | use pubsub::PubSub;
30 |
31 | //create pubsub with 5 threads. These threads run notification functions.
32 | //messages are queued indefinitely until they can be run
33 | let pubsub = PubSub::new(5);
34 |
35 | //subscribe to a channel. The subscription will stay active as long
36 | //as sub1 stays in scope
37 | let sub1 = pubsub.subscribe("channel1", move |msg|{
38 | println!("channel 1 was notified with msg: {}", msg);
39 | });
40 |
41 | //notify all subscribers of "channel1" with message "data1"
42 | //This will always return immediately since messages are queued
43 | pubsub.notify("channel1", "data1");
44 |
45 | //notify all subscribers of "channel1" except sub1
46 | sub1.notify_others("data3");
47 |
48 | //If you want to start queueing messsages for a subscription, but
49 | //don't want to process them until after some initialization
50 | let sub2_activator = pubsub.lazy_subscribe("channel2");
51 |
52 | //do some initialization here...
53 |
54 | //notifications received here are queued
55 | pubsub.notify("channel2", "data4");
56 |
57 | //this creates a subscription and processes any messages in the backlog
58 | let sub2 = sub2_activator.activate(move |msg|{
59 | println!("channel 2 was notified with msg: {}", msg);
60 | });
61 |
62 | //subscriptions can be cancelled by dropping or cancelling
63 | drop(sub1);
64 | sub2.cancel();
65 | ```
66 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate threadpool;
2 |
3 | use threadpool::ThreadPool;
4 | use std::collections::HashMap;
5 | use std::sync::{Arc, Mutex};
6 | use std::rc::Rc;
7 | use std::fmt::{Formatter, Error, Debug};
8 | use std::collections::vec_deque::VecDeque;
9 |
10 | #[cfg(test)]
11 | mod test;
12 |
13 | pub struct Subscription{
14 | pubsub: PubSub,
15 | channel_id: String,
16 | id: u64
17 | }
18 |
19 | impl Debug for Subscription{
20 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error>{
21 | fmt.write_str(&format!("Sub(channel={})", self.channel_id))
22 | }
23 | }
24 |
25 | impl Subscription{
26 | pub fn cancel(self){/* self is dropped */}
27 |
28 | pub fn notify_others(&self, msg: &str){
29 | self.pubsub.notify_exception(&self.channel_id, msg, Some(self.id));
30 | }
31 | }
32 | impl Drop for Subscription{
33 | fn drop(&mut self){
34 | self.pubsub.unregister(self);
35 | }
36 | }
37 |
38 | pub struct SubActivator{
39 | sub: Subscription
40 | }
41 | impl SubActivator{
42 | pub fn activate(self, func: F) -> Subscription
43 | where F: FnMut(String) + 'static + Send{
44 | self.sub.pubsub.activate(&self.sub.channel_id, self.sub.id, func);
45 | self.sub
46 | }
47 | }
48 |
49 | struct SubData{
50 | running: RAIIBool,
51 | backlog: VecDeque,
52 | func: Option>>>
53 | }
54 |
55 | struct InnerPubSub{
56 | channels: HashMap>,
57 | //id will stay unique for hundreds of years, even at ~1 billion/sec
58 | next_id: u64,
59 | thread_pool: Rc
60 | }
61 | #[derive(Clone)]
62 | pub struct PubSub{
63 | inner: Arc>
64 | }
65 | unsafe impl Send for PubSub{}
66 | unsafe impl Sync for PubSub{}
67 |
68 | #[derive(Clone)]
69 | struct RAIIBool{
70 | value: Arc>
71 | }
72 | impl RAIIBool{
73 | fn new(value: bool) -> RAIIBool{
74 | RAIIBool{
75 | value: Arc::new(Mutex::new(value))
76 | }
77 | }
78 | fn set(&self, value: bool) -> bool{
79 | let mut guard = self.value.lock().unwrap();
80 | let old:bool = *guard;
81 | *guard = value;
82 | old
83 | }
84 | fn new_guard(&self, value: bool) -> RAIIBoolGuard{
85 | RAIIBoolGuard::new(self.clone(), value)
86 | }
87 | }
88 |
89 | struct RAIIBoolGuard{
90 | data: RAIIBool,
91 | value: bool
92 | }
93 | impl RAIIBoolGuard{
94 | fn new(data: RAIIBool, value: bool) -> RAIIBoolGuard{
95 | RAIIBoolGuard{
96 | data: data,
97 | value: value
98 | }
99 | }
100 | fn done(self){}
101 | }
102 | impl Drop for RAIIBoolGuard{
103 | fn drop(&mut self){
104 | self.data.set(self.value);
105 | }
106 | }
107 |
108 | impl PubSub{
109 | pub fn new(num_threads: usize) -> PubSub{
110 | PubSub{
111 | inner: Arc::new(Mutex::new(InnerPubSub{
112 | channels: HashMap::new(),
113 | next_id: 0,
114 | thread_pool: Rc::new(ThreadPool::new(num_threads))
115 | }))
116 | }
117 | }
118 | fn internal_subscribe(&self, channel: &str, func: Option) -> Subscription
119 | where F: FnMut(String) + 'static + Send{
120 | let mut data = self.inner.lock().unwrap();
121 | if !data.channels.contains_key(channel){
122 | data.channels.insert(channel.to_string(), HashMap::new());
123 | }
124 | let id = data.next_id;
125 | data.next_id += 1;
126 | let sub_data = SubData{
127 | running: RAIIBool::new(false),
128 | backlog: VecDeque::new(),
129 | func: func.map(|f|Arc::new(Mutex::new(Box::new(f) as Box<_>)))
130 | };
131 |
132 | let subscriptions = data.channels.get_mut(channel).unwrap();
133 | subscriptions.insert(id, sub_data);
134 | Subscription{
135 | pubsub: self.clone(),
136 | channel_id: channel.to_string(),
137 | id: id
138 | }
139 | }
140 | pub fn subscribe(&self, channel: &str, func: F) -> Subscription
141 | where F: FnMut(String) + 'static + Send{
142 | self.internal_subscribe(channel, Some(func))
143 | }
144 |
145 | #[allow(unused_assignments)]
146 | pub fn lazy_subscribe(&self, channel: &str) -> SubActivator{
147 | let mut func = Some(|_|{});//used to give type info to 'func'
148 | func = None;
149 | SubActivator{
150 | sub: self.internal_subscribe(channel, func)
151 | }
152 | }
153 | fn activate(&self, channel: &str, id: u64, func: F)
154 | where F: FnMut(String) + 'static + Send{
155 | let mut inner = self.inner.lock().unwrap();
156 | let pool = inner.thread_pool.clone();
157 | let subs = inner.channels.get_mut(channel).unwrap();//channel will always exist
158 | let sub_data = subs.get_mut(&id).unwrap();//sub id will always exist
159 | sub_data.func = Some(Arc::new(Mutex::new(Box::new(func))));
160 | self.schedule_worker(sub_data, channel, id, &pool);
161 | }
162 | pub fn num_channels(&self) -> usize{
163 | let data = self.inner.lock().unwrap();
164 | data.channels.len()
165 | }
166 | fn unregister(&self, sub: &Subscription){
167 | let mut inner = self.inner.lock().unwrap();
168 | let mut remove_channel = false;
169 | {
170 | let sub_list = inner.channels.get_mut(&sub.channel_id).unwrap();
171 | sub_list.remove(&sub.id);
172 | if sub_list.len() == 0{
173 | remove_channel = true;
174 | }
175 | }
176 | if remove_channel{
177 | inner.channels.remove(&sub.channel_id);
178 | }
179 | }
180 | fn schedule_worker(&self, sub_data: &mut SubData, channel: &str, id: u64, pool: &Rc){
181 | if !sub_data.running.set(true){//if not currently running
182 | let thread_running = sub_data.running.clone();
183 | if let Some(func) = sub_data.func.clone(){
184 | let pubsub = self.clone();
185 | let channel = channel.to_string();
186 | let id = id.clone();
187 | pool.execute(move ||{
188 | use std::ops::DerefMut;
189 |
190 | let finish_guard = thread_running.new_guard(false);
191 | let mut guard = func.lock().unwrap();
192 | let mut func = guard.deref_mut();
193 | let mut running = true;
194 | while running{
195 | let mut notification_message = None;
196 | {
197 | let mut inner = pubsub.inner.lock().unwrap();
198 | if let Some(subs) = inner.channels.get_mut(&channel){
199 | if let Some(sub_data) = subs.get_mut(&id){
200 | if let Some(msg) = sub_data.backlog.pop_front(){
201 | notification_message = Some(msg);
202 | }
203 | }
204 | }
205 | }//unlock 'inner'
206 | if let Some(msg) = notification_message{
207 | func(msg);
208 | }else{
209 | running = false;
210 | }
211 | }
212 | finish_guard.done();
213 | });
214 | }else{
215 | thread_running.set(false);
216 | }
217 | }
218 | }
219 | pub fn notify(&self, channel: &str, msg: &str){
220 | self.notify_exception(channel, msg, None)
221 | }
222 | fn notify_exception(&self, channel: &str, msg: &str, exception: Option){
223 | let mut inner = self.inner.lock().unwrap();
224 | let pool = inner.thread_pool.clone();
225 | if let Some(subscriptions) = inner.channels.get_mut(channel){
226 | for (id,sub_data) in subscriptions{
227 | if Some(*id) != exception{
228 | sub_data.backlog.push_back(msg.to_string());
229 | self.schedule_worker(sub_data, channel, *id, &pool);
230 | }
231 | }
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/test.rs:
--------------------------------------------------------------------------------
1 | use PubSub;
2 | use std::thread::sleep;
3 | use std::sync::{Arc, Mutex};
4 | use std::time::Duration;
5 |
6 | #[test]
7 | fn basic_test() {
8 | let pubsub = PubSub::new(5);
9 | let count = Arc::new(Mutex::new(0));
10 | {
11 | let count1 = count.clone();
12 | let sub1 = pubsub.subscribe("channel1", move |_|{
13 | sleep(Duration::from_millis(1000));
14 | *count1.lock().unwrap() += 1;
15 | });
16 | let count2 = count.clone();
17 | let sub2 = pubsub.subscribe("channel2", move |_|{
18 | sleep(Duration::from_millis(1000));
19 | *count2.lock().unwrap() += 1;
20 | });
21 | pubsub.notify("channel1", "data1");
22 | pubsub.notify("channel1", "data2");
23 |
24 | pubsub.notify("channel2", "data3");
25 | pubsub.notify("channel2", "data4");
26 |
27 | sleep(Duration::from_millis(500));
28 | assert_eq!(*count.lock().unwrap(), 0);
29 | sub2.cancel();
30 | sleep(Duration::from_millis(1000));
31 | assert_eq!(*count.lock().unwrap(), 2);
32 | sleep(Duration::from_millis(1000));
33 | assert_eq!(*count.lock().unwrap(), 3);
34 | sub1.cancel();
35 | }
36 | assert!(pubsub.num_channels() == 0);
37 | }
38 |
39 | #[test]
40 | fn lazy_subscribe(){
41 | let pubsub = PubSub::new(5);
42 | let count = Arc::new(Mutex::new(0));
43 |
44 | let sub1_activator = pubsub.lazy_subscribe("channel1");
45 | pubsub.notify("channel1", "data1");
46 |
47 | let count1 = count.clone();
48 | let sub1 = sub1_activator.activate(move |msg|{
49 | assert_eq!(msg, "data1");
50 | *count1.lock().unwrap() += 1;
51 | });
52 | sleep(Duration::from_millis(500));
53 | assert_eq!(*count.lock().unwrap(), 1);
54 | sub1.cancel();
55 | }
56 |
57 | #[test]
58 | fn notify_exception() {
59 | let pubsub = PubSub::new(5);
60 | let count = Arc::new(Mutex::new(0));
61 |
62 | let count1 = count.clone();
63 | let sub1 = pubsub.subscribe("channel1", move |_|{
64 | *count1.lock().unwrap() -= 1;
65 | });
66 | let count2 = count.clone();
67 | let sub2 = pubsub.subscribe("channel1", move |_|{
68 | *count2.lock().unwrap() += 1;
69 | });
70 |
71 | sub1.notify_others("data1");
72 |
73 | sleep(Duration::from_millis(500));
74 | assert_eq!(*count.lock().unwrap(), 1);
75 | sub1.cancel();
76 | sub2.cancel();
77 | }
--------------------------------------------------------------------------------