ons 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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__
|