ons 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +7 -0
- data/.yardopts +3 -0
- data/Gemfile +4 -0
- data/README-zh_CN.md +80 -0
- data/README.md +36 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/rake +9 -0
- data/bin/setup +8 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/Action.h +12 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/ConsumeContext.h +15 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/LocalTransactionChecker.h +16 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/LocalTransactionExecuter.h +16 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/Message.h +96 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/MessageListener.h +20 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/ONSChannel.h +17 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/ONSClient.h +28 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/ONSClientException.h +27 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/ONSFactory.h +73 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/Producer.h +29 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/PushConsumer.h +22 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/SendResultONS.h +22 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/TransactionProducer.h +22 -0
- data/ext/ons/aliyun-mq-cpp-sdk/include/ons/TransactionStatus.h +15 -0
- data/ext/ons/aliyun-mq-cpp-sdk/lib/libonsclient4cpp.so +0 -0
- data/ext/ons/consumer.cpp +68 -0
- data/ext/ons/consumer.hpp +33 -0
- data/ext/ons/extconf.rb +44 -0
- data/ext/ons/listener.cpp +19 -0
- data/ext/ons/listener.hpp +25 -0
- data/ext/ons/lmfao.cpp +251 -0
- data/ext/ons/lmfao.hpp +101 -0
- data/ext/ons/ons.cpp +19 -0
- data/ext/ons/ons.hpp +7 -0
- data/ext/ons/producer.cpp +74 -0
- data/ext/ons/producer.hpp +30 -0
- data/lib/ons/consumer.rb +71 -0
- data/lib/ons/lmfao.rb +18 -0
- data/lib/ons/producer.rb +80 -0
- data/lib/ons/version.rb +3 -0
- data/lib/ons.rb +36 -0
- data/ons.gemspec +42 -0
- data/samples/consumer.rb +32 -0
- data/samples/producer.rb +35 -0
- metadata +218 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
#include <rice/Constructor.hpp>
|
2
|
+
#include <rice/Data_Type.hpp>
|
3
|
+
#include <rice/Hash.hpp>
|
4
|
+
#include <rice/Module.hpp>
|
5
|
+
#include <rice/String.hpp>
|
6
|
+
#include <rice/Symbol.hpp>
|
7
|
+
#include <ons/ONSFactory.h>
|
8
|
+
#include "listener.hpp"
|
9
|
+
#include "consumer.hpp"
|
10
|
+
|
11
|
+
Consumer::Consumer(Rice::String accessKey, Rice::String secretKey, Rice::String consumerId, Rice::Hash options)
|
12
|
+
{
|
13
|
+
ons::ONSFactoryProperty factoryInfo;
|
14
|
+
factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::AccessKey, accessKey.str());
|
15
|
+
factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::SecretKey, secretKey.str());
|
16
|
+
factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::ConsumerId, consumerId.str());
|
17
|
+
|
18
|
+
Rice::Object namesrvAddr = options.call("[]", Rice::String("namesrv_addr"));
|
19
|
+
if (!namesrvAddr) { namesrvAddr = options.call("[]", Rice::Symbol("namesrv_addr")); }
|
20
|
+
if (namesrvAddr) { factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::NAMESRV_ADDR, ((Rice::String)namesrvAddr).str()); }
|
21
|
+
|
22
|
+
Rice::Object onsAddr = options.call("[]", Rice::String("ons_addr"));
|
23
|
+
if (!onsAddr) { onsAddr = options.call("[]", Rice::Symbol("ons_addr")); }
|
24
|
+
if (onsAddr) { factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::ONSAddr, ((Rice::String)onsAddr).str()); }
|
25
|
+
|
26
|
+
Rice::Object threadNum = options.call("[]", Rice::String("thread_num"));
|
27
|
+
if (!threadNum) { threadNum = options.call("[]", Rice::Symbol("thread_num")); }
|
28
|
+
if (threadNum.rb_type() == T_FIXNUM || threadNum.rb_type() == T_BIGNUM) { threadNum = threadNum.to_s(); }
|
29
|
+
if (threadNum) { factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::ConsumeThreadNums, ((Rice::String)threadNum).str()); }
|
30
|
+
|
31
|
+
this->onsPushConsumer = ons::ONSFactory::getInstance()->createPushConsumer(factoryInfo);
|
32
|
+
}
|
33
|
+
|
34
|
+
Consumer::~Consumer()
|
35
|
+
{
|
36
|
+
for (std::vector<Listener*>::iterator iter = this->listeners.begin(); iter != this->listeners.end(); ++iter) {
|
37
|
+
delete *iter;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
void Consumer::subscribe(Rice::String topic, Rice::String subscribeExpression, Rice::Object handler)
|
42
|
+
{
|
43
|
+
Listener *listener = new Listener;
|
44
|
+
listener->setHandler(handler);
|
45
|
+
|
46
|
+
this->listeners.push_back(listener);
|
47
|
+
this->onsPushConsumer->subscribe(topic.str(), subscribeExpression.str(), listener);
|
48
|
+
}
|
49
|
+
|
50
|
+
void Consumer::start()
|
51
|
+
{
|
52
|
+
this->onsPushConsumer->start();
|
53
|
+
}
|
54
|
+
|
55
|
+
void Consumer::shutdown()
|
56
|
+
{
|
57
|
+
this->onsPushConsumer->shutdown();
|
58
|
+
}
|
59
|
+
|
60
|
+
void define_class_consumer_under_module(Rice::Module module)
|
61
|
+
{
|
62
|
+
Rice::Data_Type<Consumer> rb_cConsumer = Rice::define_class_under<Consumer>(module, "Consumer");
|
63
|
+
|
64
|
+
rb_cConsumer.define_constructor(Rice::Constructor<Consumer, Rice::String, Rice::String, Rice::String, Rice::Hash>());
|
65
|
+
rb_cConsumer.define_method("subscribe", &Consumer::subscribe);
|
66
|
+
rb_cConsumer.define_method("start", &Consumer::start);
|
67
|
+
rb_cConsumer.define_method("shutdown", &Consumer::shutdown);
|
68
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#ifndef __RBEXT_CONSUMER_HPP__
|
2
|
+
#define __RBEXT_CONSUMER_HPP__
|
3
|
+
|
4
|
+
#include <vector>
|
5
|
+
|
6
|
+
namespace Rice { class Hash; class Module; class Object; class String; }
|
7
|
+
namespace ons { class PushConsumer; }
|
8
|
+
class Listener;
|
9
|
+
|
10
|
+
class Consumer {
|
11
|
+
|
12
|
+
public:
|
13
|
+
Consumer(Rice::String accessKey, Rice::String secretKey, Rice::String consumerId, Rice::Hash options);
|
14
|
+
virtual ~Consumer();
|
15
|
+
|
16
|
+
virtual void subscribe(Rice::String topic, Rice::String subscribeExpression, Rice::Object handler);
|
17
|
+
|
18
|
+
// call this method once after subscribe topic.
|
19
|
+
virtual void start();
|
20
|
+
|
21
|
+
// call this method before program exit,
|
22
|
+
// otherwise it would cause a memory leak and some other issues.
|
23
|
+
virtual void shutdown();
|
24
|
+
|
25
|
+
private:
|
26
|
+
ons::PushConsumer* onsPushConsumer;
|
27
|
+
std::vector<Listener*> listeners;
|
28
|
+
|
29
|
+
};
|
30
|
+
|
31
|
+
void define_class_consumer_under_module(Rice::Module module);
|
32
|
+
|
33
|
+
#endif // __RBEXT_CONSUMER_HPP__
|
data/ext/ons/extconf.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# rubocop:disable Style/GlobalVars
|
2
|
+
|
3
|
+
# use mkmf-rice instead mkmf
|
4
|
+
require 'mkmf-rice'
|
5
|
+
|
6
|
+
# aliyun mq sdk dir
|
7
|
+
ALIYUN_MQ_CPP_SDK_DIR = File.expand_path('../aliyun-mq-cpp-sdk', __FILE__)
|
8
|
+
|
9
|
+
# header dirs to search
|
10
|
+
HEADER_DIRS = [
|
11
|
+
'/opt/local/include', # search /opt/local for macports
|
12
|
+
'/usr/local/include', # search /usr/local for people that installed from source
|
13
|
+
RbConfig::CONFIG['includedir'], # check the ruby install locations
|
14
|
+
File.join(ALIYUN_MQ_CPP_SDK_DIR, 'include'),
|
15
|
+
'/usr/include', # finally fall back to /usr
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
# library dirs to search
|
19
|
+
LIB_DIRS = [
|
20
|
+
'/opt/local/lib', # search /opt/local for macports
|
21
|
+
'/usr/local/lib', # search /usr/local for people that installed from source
|
22
|
+
RbConfig::CONFIG['libdir'], # check the ruby install locations
|
23
|
+
File.join(ALIYUN_MQ_CPP_SDK_DIR, 'lib'),
|
24
|
+
'/usr/lib', # finally fall back to /usr
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
# configure cxxflags
|
28
|
+
$warnflags.gsub!(/-Wdeclaration-after-statement/, '') # this flag is valid for C/ObjC but not for C++
|
29
|
+
$warnflags.gsub!(/-Wimplicit-function-declaration/, '') # this flag is valid for C/ObjC but not for C++
|
30
|
+
$warnflags << ' -Wno-ignored-qualifiers'
|
31
|
+
|
32
|
+
# configure various "with" options
|
33
|
+
dir_config('boost', HEADER_DIRS, LIB_DIRS)
|
34
|
+
dir_config('onsclient4cpp', HEADER_DIRS, LIB_DIRS)
|
35
|
+
|
36
|
+
# configure link libraries
|
37
|
+
abort 'libboost_chrono is missing, please install libboost_chrono' unless have_library('boost_chrono')
|
38
|
+
abort 'libboost_system is missing, please install libboost_system' unless have_library('boost_system')
|
39
|
+
abort 'libboost_thread is missing, please install libboost_thread' unless have_library('boost_thread')
|
40
|
+
abort unless have_library('onsclient4cpp')
|
41
|
+
abort unless have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
|
42
|
+
|
43
|
+
# create Makefile
|
44
|
+
create_makefile('ons/ons')
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#include <rice/Hash.hpp>
|
2
|
+
#include "listener.hpp"
|
3
|
+
#include "lmfao.hpp"
|
4
|
+
|
5
|
+
void Listener::setHandler(Rice::Object handler)
|
6
|
+
{
|
7
|
+
this->handler = handler.value();
|
8
|
+
this->riceHandler = handler;
|
9
|
+
}
|
10
|
+
|
11
|
+
Action Listener::consume(ons::Message& message, ons::ConsumeContext& context)
|
12
|
+
{
|
13
|
+
// Listener::consume will called in a separate thread which is not created by Ruby,
|
14
|
+
// so you cannot call any Ruby functions here. Use the LMFAO library workaround.
|
15
|
+
bool result = mLMFAO_call(this->handler, &message);
|
16
|
+
|
17
|
+
if (result) { return CommitMessage; }
|
18
|
+
return ReconsumeLater;
|
19
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#ifndef __RBEXT_LISTENER_HPP__
|
2
|
+
#define __RBEXT_LISTENER_HPP__
|
3
|
+
|
4
|
+
#include <rice/Object.hpp>
|
5
|
+
#include <ons/MessageListener.h>
|
6
|
+
|
7
|
+
class Listener: public ons::MessageListener {
|
8
|
+
|
9
|
+
public:
|
10
|
+
Listener() {}
|
11
|
+
virtual ~Listener() {}
|
12
|
+
|
13
|
+
virtual void setHandler(Rice::Object handler);
|
14
|
+
|
15
|
+
// implement the interface of consuming message.
|
16
|
+
virtual Action consume(ons::Message& message, ons::ConsumeContext& context);
|
17
|
+
|
18
|
+
private:
|
19
|
+
// a object which should be repsond to :call method.
|
20
|
+
VALUE handler;
|
21
|
+
Rice::Object riceHandler;
|
22
|
+
|
23
|
+
};
|
24
|
+
|
25
|
+
#endif // __RBEXT_LISTENER_HPP__
|
data/ext/ons/lmfao.cpp
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
#include "lmfao.hpp"
|
2
|
+
|
3
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
4
|
+
* Functions related to the global callback queue
|
5
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
6
|
+
|
7
|
+
/*
|
8
|
+
Three globals to allow for Ruby/C-thread communication:
|
9
|
+
|
10
|
+
- mutex & condition to synchronize access to callback_queue
|
11
|
+
- callback_queue to store actual callback data in
|
12
|
+
|
13
|
+
Be careful with the functions that manipulate the callback
|
14
|
+
queue; they must do so in the protection of a mutex.
|
15
|
+
*/
|
16
|
+
pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER;
|
17
|
+
pthread_cond_t g_callback_cond = PTHREAD_COND_INITIALIZER;
|
18
|
+
callback_t *g_callback_queue = NULL;
|
19
|
+
|
20
|
+
/*
|
21
|
+
Use this function to add a callback node onto the global
|
22
|
+
callback queue.
|
23
|
+
|
24
|
+
Do note that we are adding items to the front of the linked
|
25
|
+
list, and as such events will always be handled by most recent
|
26
|
+
first. To remedy this, add to the end of the queue instead.
|
27
|
+
*/
|
28
|
+
void g_callback_queue_push(callback_t *callback)
|
29
|
+
{
|
30
|
+
callback->next = g_callback_queue;
|
31
|
+
g_callback_queue = callback;
|
32
|
+
}
|
33
|
+
|
34
|
+
/*
|
35
|
+
Use this function to pop off a callback node from the
|
36
|
+
global callback queue. Returns NULL if queue is empty.
|
37
|
+
*/
|
38
|
+
callback_t *g_callback_queue_pop()
|
39
|
+
{
|
40
|
+
callback_t *callback = g_callback_queue;
|
41
|
+
if (callback)
|
42
|
+
{
|
43
|
+
g_callback_queue = callback->next;
|
44
|
+
}
|
45
|
+
return callback;
|
46
|
+
}
|
47
|
+
|
48
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
49
|
+
* Functions related to LMFAO Ruby API
|
50
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
51
|
+
|
52
|
+
/*
|
53
|
+
Call LMFAO with the given argument.
|
54
|
+
*/
|
55
|
+
bool mLMFAO_call(VALUE handler, ons::Message* message)
|
56
|
+
{
|
57
|
+
listener_consume_args_t args = {
|
58
|
+
.handler = handler,
|
59
|
+
.message = message
|
60
|
+
};
|
61
|
+
return lmfao_callback((void *) &args);
|
62
|
+
}
|
63
|
+
|
64
|
+
/*
|
65
|
+
This is our user-defined C callback, it gets called by the C library.
|
66
|
+
|
67
|
+
We need to:
|
68
|
+
|
69
|
+
1. Create a callback structure, put our parameters in it
|
70
|
+
2. Push the callback node onto the global callback queue
|
71
|
+
3. Wait for the callback to be handled
|
72
|
+
4. Return the return value
|
73
|
+
*/
|
74
|
+
void *lmfao_callback(void *data)
|
75
|
+
{
|
76
|
+
callback_t callback;
|
77
|
+
pthread_mutex_init(&callback.mutex, NULL);
|
78
|
+
pthread_cond_init(&callback.cond, NULL);
|
79
|
+
callback.data = data;
|
80
|
+
callback.handled = false;
|
81
|
+
|
82
|
+
// Put callback data in global callback queue
|
83
|
+
pthread_mutex_lock(&g_callback_mutex);
|
84
|
+
g_callback_queue_push(&callback);
|
85
|
+
pthread_mutex_unlock(&g_callback_mutex);
|
86
|
+
|
87
|
+
// Notify waiting Ruby thread that we have callback data
|
88
|
+
pthread_cond_signal(&g_callback_cond);
|
89
|
+
|
90
|
+
// Wait for callback to be handled
|
91
|
+
pthread_mutex_lock(&callback.mutex);
|
92
|
+
while (callback.handled == false)
|
93
|
+
{
|
94
|
+
pthread_cond_wait(&callback.cond, &callback.mutex);
|
95
|
+
}
|
96
|
+
pthread_mutex_unlock(&callback.mutex);
|
97
|
+
|
98
|
+
// Clean up
|
99
|
+
pthread_mutex_destroy(&callback.mutex);
|
100
|
+
pthread_cond_destroy(&callback.cond);
|
101
|
+
|
102
|
+
return callback.data;
|
103
|
+
}
|
104
|
+
|
105
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
106
|
+
* Our special Ruby event-listening thread functions
|
107
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
108
|
+
|
109
|
+
/*
|
110
|
+
Executed for each callback notification; what we receive
|
111
|
+
are the callback parameters. The job of this method is to:
|
112
|
+
|
113
|
+
1. Convert callback parameters into Ruby values
|
114
|
+
2. Call the appropriate callback with said parameters
|
115
|
+
3. Convert the Ruby return value into a C value
|
116
|
+
4. Hand over the C value to the C callback
|
117
|
+
*/
|
118
|
+
VALUE LMFAO_handle_callback(void *cb)
|
119
|
+
{
|
120
|
+
callback_t *callback = (callback_t*) cb;
|
121
|
+
|
122
|
+
// figure out the proper handler for this particular event, as
|
123
|
+
// well as convert the callback data into proper ruby values!
|
124
|
+
listener_consume_args_t *args = (listener_consume_args_t*) callback->data;
|
125
|
+
ons::Message *message = args->message;
|
126
|
+
VALUE proc = args->handler;
|
127
|
+
VALUE hash = rb_hash_new();
|
128
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("id")), rb_str_new2(message->getMsgID().c_str()));
|
129
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("topic")), rb_str_new2(message->getTopic().c_str()));
|
130
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("tag")), rb_str_new2(message->getTag().c_str()));
|
131
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("body")), rb_str_new2(message->getBody().c_str()));
|
132
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("key")), rb_str_new2(message->getKey().c_str()));
|
133
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("reconsume_times")), INT2NUM(message->getReconsumeTimes()));
|
134
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("store_timestamp")), LL2NUM(message->getStoreTimestamp()));
|
135
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("deliver_timestamp")), LL2NUM(message->getStartDeliverTime()));
|
136
|
+
VALUE result = rb_funcall(proc, rb_intern("call"), 1, hash);
|
137
|
+
|
138
|
+
// convert it to a C value that our callback can return
|
139
|
+
if (result == Qnil || result == Qfalse) {
|
140
|
+
callback->data = (void *) false;
|
141
|
+
} else {
|
142
|
+
callback->data = (void *) true;
|
143
|
+
}
|
144
|
+
|
145
|
+
// tell the callback that it has been handled, we are done
|
146
|
+
pthread_mutex_lock(&callback->mutex);
|
147
|
+
callback->handled = true;
|
148
|
+
pthread_cond_signal(&callback->cond);
|
149
|
+
pthread_mutex_unlock(&callback->mutex);
|
150
|
+
|
151
|
+
return Qnil;
|
152
|
+
}
|
153
|
+
|
154
|
+
/*
|
155
|
+
Wait for global callback queue to contain something.
|
156
|
+
|
157
|
+
This function is called while not holding the GVL, so it’s safe
|
158
|
+
to do long waits in here; other threads will still run.
|
159
|
+
|
160
|
+
The job of this function is merely to wait until the callback queue
|
161
|
+
contains something. Once that happens, we remove the item from the
|
162
|
+
queue and return it to our caller through waiting->callback.
|
163
|
+
*/
|
164
|
+
void *wait_for_callback_signal(void *w)
|
165
|
+
{
|
166
|
+
callback_waiting_t *waiting = (callback_waiting_t*) w;
|
167
|
+
|
168
|
+
pthread_mutex_lock(&g_callback_mutex);
|
169
|
+
|
170
|
+
// abort signal is used when ruby wants us to stop waiting
|
171
|
+
while (waiting->abort == false && (waiting->callback = g_callback_queue_pop()) == NULL)
|
172
|
+
{
|
173
|
+
pthread_cond_wait(&g_callback_cond, &g_callback_mutex);
|
174
|
+
}
|
175
|
+
|
176
|
+
pthread_mutex_unlock(&g_callback_mutex);
|
177
|
+
|
178
|
+
return NULL;
|
179
|
+
}
|
180
|
+
|
181
|
+
/*
|
182
|
+
Stop waiting for callback notification. This function
|
183
|
+
is invoked by Ruby when she wants us to exit.
|
184
|
+
|
185
|
+
As `wait_for_callback_signal` function is executed without holding
|
186
|
+
the GVL, it can potentially take forever. However, when somebody wants
|
187
|
+
to quit the program (for example if somebody presses CTRL-C to exit)
|
188
|
+
we must tell Ruby how to wake up the `wait_for_callback_signal` function
|
189
|
+
so Ruby can exit properly.
|
190
|
+
*/
|
191
|
+
void stop_waiting_for_callback_signal(void *w)
|
192
|
+
{
|
193
|
+
callback_waiting_t *waiting = (callback_waiting_t*) w;
|
194
|
+
|
195
|
+
pthread_mutex_lock(&g_callback_mutex);
|
196
|
+
waiting->abort = true;
|
197
|
+
pthread_cond_signal(&g_callback_cond);
|
198
|
+
pthread_mutex_unlock(&g_callback_mutex);
|
199
|
+
}
|
200
|
+
|
201
|
+
/*
|
202
|
+
This thread loops continously, waiting for callbacks to happen in C.
|
203
|
+
Once they do, it will handle the callback in a new thread.
|
204
|
+
|
205
|
+
The reason we use a thread for handling the callback is that the handler
|
206
|
+
itself might fire off more callbacks, that might need to be handled before
|
207
|
+
the handler returns. We can’t do that if the event thread is busy handling
|
208
|
+
the first callback.
|
209
|
+
|
210
|
+
Continously, we need to:
|
211
|
+
|
212
|
+
1. Unlock the Ruby GVL (allowing other threads to run while we wait)
|
213
|
+
2. Wait for a callback to fire
|
214
|
+
3. Spawn a new ruby thread to handle the callback
|
215
|
+
*/
|
216
|
+
VALUE LMFAO_event_thread(void *unused)
|
217
|
+
{
|
218
|
+
callback_waiting_t waiting = {
|
219
|
+
.callback = NULL,
|
220
|
+
.abort = false
|
221
|
+
};
|
222
|
+
|
223
|
+
while (waiting.abort == false)
|
224
|
+
{
|
225
|
+
// release the GVL while waiting for a callback notification
|
226
|
+
rb_thread_call_without_gvl(wait_for_callback_signal, &waiting, stop_waiting_for_callback_signal, &waiting);
|
227
|
+
|
228
|
+
// if ruby wants us to abort, this will be NULL
|
229
|
+
if (waiting.callback)
|
230
|
+
{
|
231
|
+
rb_thread_create((VALUE (*)(...)) LMFAO_handle_callback, (void *) waiting.callback);
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
return Qnil;
|
236
|
+
}
|
237
|
+
|
238
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
239
|
+
* Ruby bindings for LMFAO C library
|
240
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
241
|
+
|
242
|
+
Rice::Object start_event_thread()
|
243
|
+
{
|
244
|
+
return rb_thread_create((VALUE (*)(...)) LMFAO_event_thread, NULL);
|
245
|
+
}
|
246
|
+
|
247
|
+
void define_module_lmfao_under_module(Rice::Module module)
|
248
|
+
{
|
249
|
+
Rice::Module rb_mLMFAO = module.define_module("LMFAO");
|
250
|
+
rb_mLMFAO.define_singleton_method("start_event_thread", &start_event_thread);
|
251
|
+
}
|
data/ext/ons/lmfao.hpp
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#ifndef __RBEXT_LMFAO_HPP__
|
2
|
+
#define __RBEXT_LMFAO_HPP__
|
3
|
+
|
4
|
+
/*
|
5
|
+
Problem:
|
6
|
+
|
7
|
+
You cannot call Ruby functions from a thread not created by Ruby. When it comes to MRI, you
|
8
|
+
must be the owner of the GVL to call ruby functions, and since the thread is not a
|
9
|
+
ruby-created thread you have no way of owning the GVL.
|
10
|
+
|
11
|
+
Workaround:
|
12
|
+
|
13
|
+
1. we have a special ruby thread, waiting to be notified
|
14
|
+
2. when C callback is invoked, it stores its’ parameters somewhere, notifies ruby thread and waits
|
15
|
+
3. ruby thread is notified, reads the callback parameters, and executes the callback handler
|
16
|
+
4. ruby thread puts the return value of the handler in location where C callback can reach it, and notifies C callback
|
17
|
+
5. C callback wakes up again, reads return value and returns it
|
18
|
+
|
19
|
+
Reference:
|
20
|
+
|
21
|
+
* http://www.ruby-forum.com/topic/3149838
|
22
|
+
* http://www.burgestrand.se/articles/asynchronous-callbacks-in-ruby-c-extensions
|
23
|
+
*/
|
24
|
+
|
25
|
+
#include <rice/Module.hpp>
|
26
|
+
#include <ons/Message.h>
|
27
|
+
#include <ruby/thread.h>
|
28
|
+
#include <pthread.h>
|
29
|
+
|
30
|
+
typedef struct listener_consume_args_t listener_consume_args_t;
|
31
|
+
struct listener_consume_args_t {
|
32
|
+
VALUE handler;
|
33
|
+
ons::Message *message;
|
34
|
+
};
|
35
|
+
|
36
|
+
typedef struct callback_t callback_t;
|
37
|
+
struct callback_t {
|
38
|
+
/*
|
39
|
+
Each callback needs to store its’ data somewhere; this
|
40
|
+
is how we later on access that data from our Ruby thread.
|
41
|
+
|
42
|
+
We also use this for our return value from the Ruby handler.
|
43
|
+
*/
|
44
|
+
void *data;
|
45
|
+
|
46
|
+
/*
|
47
|
+
Once we’ve dispatched our callback data to Ruby, we must
|
48
|
+
wait for a reply before we continue. These two are used
|
49
|
+
for that purpose.
|
50
|
+
*/
|
51
|
+
pthread_mutex_t mutex;
|
52
|
+
pthread_cond_t cond;
|
53
|
+
|
54
|
+
/*
|
55
|
+
Even though we use the condition variable above to wait,
|
56
|
+
we might still be woken up (spurious wakeups). This bool
|
57
|
+
serves as a final check that tells us if we can continue.
|
58
|
+
*/
|
59
|
+
bool handled;
|
60
|
+
|
61
|
+
/*
|
62
|
+
We use this to implement a linked list of callback data.
|
63
|
+
This allows multiple callbacks to happen simultaneously
|
64
|
+
without them having to wait for each other.
|
65
|
+
*/
|
66
|
+
callback_t *next;
|
67
|
+
};
|
68
|
+
|
69
|
+
typedef struct callback_waiting_t callback_waiting_t;
|
70
|
+
struct callback_waiting_t {
|
71
|
+
callback_t *callback;
|
72
|
+
bool abort;
|
73
|
+
};
|
74
|
+
|
75
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
76
|
+
* Functions related to the global callback queue
|
77
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
78
|
+
void g_callback_queue_push(callback_t *callback);
|
79
|
+
callback_t *g_callback_queue_pop();
|
80
|
+
|
81
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
82
|
+
* Functions related to LMFAO Ruby API
|
83
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
84
|
+
bool mLMFAO_call(VALUE handler, ons::Message* message);
|
85
|
+
void *lmfao_callback(void *data);
|
86
|
+
|
87
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
88
|
+
* Our special Ruby event-listening thread functions
|
89
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
90
|
+
VALUE LMFAO_handle_callback(void *cb);
|
91
|
+
void *wait_for_callback_signal(void *w);
|
92
|
+
void stop_waiting_for_callback_signal(void *w);
|
93
|
+
VALUE LMFAO_event_thread(void *unused);
|
94
|
+
|
95
|
+
/* * * * * * * * * * * * * * * * * * * * * * * * *
|
96
|
+
* Ruby bindings for LMFAO C library
|
97
|
+
* * * * * * * * * * * * * * * * * * * * * * * * */
|
98
|
+
Rice::Object start_event_thread();
|
99
|
+
void define_module_lmfao_under_module(Rice::Module module);
|
100
|
+
|
101
|
+
#endif // __RBEXT_LMFAO_HPP__
|
data/ext/ons/ons.cpp
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#include <rice/Module.hpp>
|
2
|
+
#include "lmfao.hpp"
|
3
|
+
#include "consumer.hpp"
|
4
|
+
#include "producer.hpp"
|
5
|
+
#include "ons.hpp"
|
6
|
+
|
7
|
+
extern "C"
|
8
|
+
void Init_ons()
|
9
|
+
{
|
10
|
+
RUBY_TRY
|
11
|
+
{
|
12
|
+
Rice::Module rb_mOns = Rice::define_module("Ons");
|
13
|
+
Rice::Module rb_mInternal = rb_mOns.define_module("Internal");
|
14
|
+
define_class_consumer_under_module(rb_mInternal);
|
15
|
+
define_class_producer_under_module(rb_mInternal);
|
16
|
+
define_module_lmfao_under_module(rb_mInternal);
|
17
|
+
}
|
18
|
+
RUBY_CATCH
|
19
|
+
}
|
data/ext/ons/ons.hpp
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#include <rice/Constructor.hpp>
|
2
|
+
#include <rice/Data_Type.hpp>
|
3
|
+
#include <rice/Hash.hpp>
|
4
|
+
#include <rice/Module.hpp>
|
5
|
+
#include <rice/Object.hpp>
|
6
|
+
#include <rice/String.hpp>
|
7
|
+
#include <rice/Symbol.hpp>
|
8
|
+
#include <ons/ONSFactory.h>
|
9
|
+
#include "producer.hpp"
|
10
|
+
|
11
|
+
Producer::Producer(Rice::String accessKey, Rice::String secretKey, Rice::String producerId, Rice::Hash options)
|
12
|
+
{
|
13
|
+
ons::ONSFactoryProperty factoryInfo;
|
14
|
+
factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::AccessKey, accessKey.str());
|
15
|
+
factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::SecretKey, secretKey.str());
|
16
|
+
factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::ProducerId, producerId.str());
|
17
|
+
|
18
|
+
Rice::Object namesrvAddr = options.call("[]", Rice::String("namesrv_addr"));
|
19
|
+
if (!namesrvAddr) { namesrvAddr = options.call("[]", Rice::Symbol("namesrv_addr")); }
|
20
|
+
if (namesrvAddr) { factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::NAMESRV_ADDR, ((Rice::String)namesrvAddr).str()); }
|
21
|
+
|
22
|
+
Rice::Object onsAddr = options.call("[]", Rice::String("ons_addr"));
|
23
|
+
if (!onsAddr) { onsAddr = options.call("[]", Rice::Symbol("ons_addr")); }
|
24
|
+
if (onsAddr) { factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::ONSAddr, ((Rice::String)onsAddr).str()); }
|
25
|
+
|
26
|
+
Rice::Object sendTimeout = options.call("[]", Rice::String("send_timeout"));
|
27
|
+
if (!sendTimeout) { sendTimeout = options.call("[]", Rice::Symbol("send_timeout")); }
|
28
|
+
if (sendTimeout.rb_type() == T_FIXNUM || sendTimeout.rb_type() == T_BIGNUM) { sendTimeout = sendTimeout.to_s(); }
|
29
|
+
if (sendTimeout) { factoryInfo.setFactoryProperty(ons::ONSFactoryProperty::SendMsgTimeoutMillis, ((Rice::String)sendTimeout).str()); }
|
30
|
+
|
31
|
+
this->onsProducer = ons::ONSFactory::getInstance()->createProducer(factoryInfo);
|
32
|
+
}
|
33
|
+
|
34
|
+
Rice::String Producer::sendMessage(Rice::String topic, Rice::String tag, Rice::String body, Rice::String key)
|
35
|
+
{
|
36
|
+
ons::Message message(topic.str(), tag.str(), body.str());
|
37
|
+
if (key.length() != 0) { message.setKey(key.str()); }
|
38
|
+
|
39
|
+
ons::SendResultONS sendResult = this->onsProducer->send(message);
|
40
|
+
return sendResult.getMessageId();
|
41
|
+
}
|
42
|
+
|
43
|
+
Rice::String Producer::sendTimerMessage(Rice::String topic, Rice::String tag, Rice::String body, Rice::Object deliverTimestamp, Rice::String key)
|
44
|
+
{
|
45
|
+
ons::Message message(topic.str(), tag.str(), body.str());
|
46
|
+
if (key.length() != 0) { message.setKey(key.str()); }
|
47
|
+
|
48
|
+
long long timestamp = NUM2LL(deliverTimestamp.value());
|
49
|
+
if (timestamp != 0) { message.setStartDeliverTime(timestamp); }
|
50
|
+
|
51
|
+
ons::SendResultONS sendResult = this->onsProducer->send(message);
|
52
|
+
return sendResult.getMessageId();
|
53
|
+
}
|
54
|
+
|
55
|
+
void Producer::start()
|
56
|
+
{
|
57
|
+
this->onsProducer->start();
|
58
|
+
}
|
59
|
+
|
60
|
+
void Producer::shutdown()
|
61
|
+
{
|
62
|
+
this->onsProducer->shutdown();
|
63
|
+
}
|
64
|
+
|
65
|
+
void define_class_producer_under_module(Rice::Module module)
|
66
|
+
{
|
67
|
+
Rice::Data_Type<Producer> rb_cProducer = Rice::define_class_under<Producer>(module, "Producer");
|
68
|
+
|
69
|
+
rb_cProducer.define_constructor(Rice::Constructor<Producer, Rice::String, Rice::String, Rice::String, Rice::Hash>());
|
70
|
+
rb_cProducer.define_method("send_message", &Producer::sendMessage);
|
71
|
+
rb_cProducer.define_method("send_timer_message", &Producer::sendTimerMessage);
|
72
|
+
rb_cProducer.define_method("start", &Producer::start);
|
73
|
+
rb_cProducer.define_method("shutdown", &Producer::shutdown);
|
74
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#ifndef __RBEXT_PRODUCER_HPP__
|
2
|
+
#define __RBEXT_PRODUCER_HPP__
|
3
|
+
|
4
|
+
namespace Rice { class Hash; class Module; class Object; class String; }
|
5
|
+
namespace ons { class Producer; }
|
6
|
+
|
7
|
+
class Producer {
|
8
|
+
|
9
|
+
public:
|
10
|
+
Producer(Rice::String accessKey, Rice::String secretKey, Rice::String producerId, Rice::Hash options);
|
11
|
+
virtual ~Producer() {}
|
12
|
+
|
13
|
+
virtual Rice::String sendMessage(Rice::String topic, Rice::String tag, Rice::String body, Rice::String key);
|
14
|
+
virtual Rice::String sendTimerMessage(Rice::String topic, Rice::String tag, Rice::String body, Rice::Object deliverTimestamp, Rice::String key);
|
15
|
+
|
16
|
+
// call this method once before send any messages.
|
17
|
+
virtual void start();
|
18
|
+
|
19
|
+
// call this method before program exit,
|
20
|
+
// otherwise it would cause a memory leak and some other issues.
|
21
|
+
virtual void shutdown();
|
22
|
+
|
23
|
+
private:
|
24
|
+
ons::Producer* onsProducer;
|
25
|
+
|
26
|
+
};
|
27
|
+
|
28
|
+
void define_class_producer_under_module(Rice::Module module);
|
29
|
+
|
30
|
+
#endif // __RBEXT_PRODUCER_HPP__
|