hermann 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +33 -0
- data/ext/hermann/extconf.rb +74 -0
- data/ext/hermann/hermann_lib.c +1031 -0
- data/ext/hermann/hermann_lib.h +107 -0
- data/ext/patches/librdkafka/0006-Update-some-headers-to-include-the-right-headers-to-.patch +57 -0
- data/lib/hermann/consumer.rb +19 -0
- data/lib/hermann/errors.rb +8 -0
- data/lib/hermann/producer.rb +122 -0
- data/lib/hermann/result.rb +74 -0
- data/lib/hermann/timeout.rb +37 -0
- data/lib/hermann/version.rb +3 -0
- data/lib/hermann.rb +2 -0
- metadata +76 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
/*
|
2
|
+
* hermann_lib.h - Ruby wrapper for the librdkafka library
|
3
|
+
*
|
4
|
+
* Copyright (c) 2014 Stan Campbell
|
5
|
+
* Copyright (c) 2014 Lookout, Inc.
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without
|
9
|
+
* modification, are permitted provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer.
|
13
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
14
|
+
* this list of conditions and the following disclaimer in the documentation
|
15
|
+
* and/or other materials provided with the distribution.
|
16
|
+
*
|
17
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
* POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
*/
|
29
|
+
|
30
|
+
#ifndef HERMANN_H
|
31
|
+
#define HERMANN_H
|
32
|
+
|
33
|
+
#include <ruby.h>
|
34
|
+
|
35
|
+
#include <ctype.h>
|
36
|
+
#include <signal.h>
|
37
|
+
#include <string.h>
|
38
|
+
#include <unistd.h>
|
39
|
+
#include <stdlib.h>
|
40
|
+
#include <syslog.h>
|
41
|
+
#include <sys/time.h>
|
42
|
+
#include <errno.h>
|
43
|
+
|
44
|
+
#include <librdkafka/rdkafka.h>
|
45
|
+
|
46
|
+
#ifdef TRACE
|
47
|
+
#define TRACER(...) do { \
|
48
|
+
fprintf(stderr, "%i:%s()> ", __LINE__, __PRETTY_FUNCTION__); \
|
49
|
+
fprintf(stderr, __VA_ARGS__); \
|
50
|
+
fflush(stderr); \
|
51
|
+
} while (0)
|
52
|
+
#else
|
53
|
+
#define TRACER(...) do { } while (0)
|
54
|
+
#endif
|
55
|
+
|
56
|
+
// Holds the defined Ruby module for Hermann
|
57
|
+
static VALUE hermann_module;
|
58
|
+
|
59
|
+
#define HERMANN_MAX_ERRSTR_LEN 512
|
60
|
+
|
61
|
+
static int DEBUG = 0;
|
62
|
+
|
63
|
+
// Should we expect rb_thread_blocking_region to be present?
|
64
|
+
// #define RB_THREAD_BLOCKING_REGION
|
65
|
+
#undef RB_THREAD_BLOCKING_REGION
|
66
|
+
|
67
|
+
static enum {
|
68
|
+
OUTPUT_HEXDUMP,
|
69
|
+
OUTPUT_RAW,
|
70
|
+
} output = OUTPUT_HEXDUMP;
|
71
|
+
|
72
|
+
typedef struct HermannInstanceConfig {
|
73
|
+
char *topic;
|
74
|
+
|
75
|
+
/* Kafka configuration */
|
76
|
+
rd_kafka_t *rk;
|
77
|
+
rd_kafka_topic_t *rkt;
|
78
|
+
char *brokers;
|
79
|
+
int partition;
|
80
|
+
rd_kafka_topic_conf_t *topic_conf;
|
81
|
+
char errstr[512];
|
82
|
+
rd_kafka_conf_t *conf;
|
83
|
+
const char *debug;
|
84
|
+
int64_t start_offset;
|
85
|
+
int do_conf_dump;
|
86
|
+
|
87
|
+
int run;
|
88
|
+
int exit_eof;
|
89
|
+
int quiet;
|
90
|
+
|
91
|
+
int isInitialized;
|
92
|
+
int isConnected;
|
93
|
+
|
94
|
+
int isErrored;
|
95
|
+
char *error;
|
96
|
+
} HermannInstanceConfig;
|
97
|
+
|
98
|
+
typedef HermannInstanceConfig hermann_conf_t;
|
99
|
+
|
100
|
+
typedef struct {
|
101
|
+
/* Hermann::Lib::Producer */
|
102
|
+
hermann_conf_t *producer;
|
103
|
+
/* Hermann::Result */
|
104
|
+
VALUE result;
|
105
|
+
} hermann_push_ctx_t;
|
106
|
+
|
107
|
+
#endif
|
@@ -0,0 +1,57 @@
|
|
1
|
+
From 888ca33b571d99e877d665235b822f7c961c8fdb Mon Sep 17 00:00:00 2001
|
2
|
+
From: "R. Tyler Croy" <tyler@monkeypox.org>
|
3
|
+
Date: Thu, 28 Aug 2014 16:24:04 -0700
|
4
|
+
Subject: [PATCH 6/8] Update some headers to include the right headers to build
|
5
|
+
on FreeBSD
|
6
|
+
|
7
|
+
---
|
8
|
+
src/rd.h | 9 +++++++++
|
9
|
+
src/rdaddr.h | 4 ++++
|
10
|
+
2 files changed, 13 insertions(+)
|
11
|
+
|
12
|
+
diff --git a/src/rd.h b/src/rd.h
|
13
|
+
index c31501e..4789493 100644
|
14
|
+
--- a/src/rd.h
|
15
|
+
+++ b/src/rd.h
|
16
|
+
@@ -37,7 +37,11 @@
|
17
|
+
#include <errno.h>
|
18
|
+
#include <time.h>
|
19
|
+
#include <sys/time.h>
|
20
|
+
+
|
21
|
+
+#ifndef __FreeBSD__
|
22
|
+
+/* alloca(3) is in stdlib on FreeBSD */
|
23
|
+
#include <alloca.h>
|
24
|
+
+#endif
|
25
|
+
#include <assert.h>
|
26
|
+
#include <pthread.h>
|
27
|
+
|
28
|
+
@@ -110,6 +114,11 @@
|
29
|
+
# endif
|
30
|
+
#endif /* sun */
|
31
|
+
|
32
|
+
+#ifdef __FreeBSD__
|
33
|
+
+/* FreeBSD defines be64toh() in sys/endian.h */
|
34
|
+
+#include <sys/endian.h>
|
35
|
+
+#endif
|
36
|
+
+
|
37
|
+
#ifndef be64toh
|
38
|
+
#ifndef __APPLE__
|
39
|
+
#ifndef sun
|
40
|
+
diff --git a/src/rdaddr.h b/src/rdaddr.h
|
41
|
+
index 0b37354..e55bd55 100644
|
42
|
+
--- a/src/rdaddr.h
|
43
|
+
+++ b/src/rdaddr.h
|
44
|
+
@@ -32,6 +32,10 @@
|
45
|
+
#include <arpa/inet.h>
|
46
|
+
#include <netdb.h>
|
47
|
+
|
48
|
+
+#ifdef __FreeBSD__
|
49
|
+
+#include <sys/socket.h>
|
50
|
+
+#endif
|
51
|
+
+
|
52
|
+
/**
|
53
|
+
* rd_sockaddr_inx_t is a union for either ipv4 or ipv6 sockaddrs.
|
54
|
+
* It provides conveniant abstraction of AF_INET* agnostic operations.
|
55
|
+
--
|
56
|
+
1.9.0
|
57
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'hermann'
|
2
|
+
require 'hermann_lib'
|
3
|
+
|
4
|
+
module Hermann
|
5
|
+
class Consumer
|
6
|
+
attr_reader :topic, :brokers, :partition, :internal
|
7
|
+
|
8
|
+
def initialize(topic, brokers, partition)
|
9
|
+
@topic = topic
|
10
|
+
@brokers = brokers
|
11
|
+
@partition = partition
|
12
|
+
@internal = Hermann::Lib::Consumer.new(topic, brokers, partition)
|
13
|
+
end
|
14
|
+
|
15
|
+
def consume(&block)
|
16
|
+
@internal.consume(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'hermann'
|
2
|
+
require 'hermann/result'
|
3
|
+
require 'hermann_lib'
|
4
|
+
|
5
|
+
module Hermann
|
6
|
+
class Producer
|
7
|
+
attr_reader :topic, :brokers, :internal, :children
|
8
|
+
|
9
|
+
def initialize(topic, brokers)
|
10
|
+
@topic = topic
|
11
|
+
@brokers = brokers
|
12
|
+
@internal = Hermann::Lib::Producer.new(topic, brokers)
|
13
|
+
# We're tracking children so we can make sure that at Producer exit we
|
14
|
+
# make a reasonable attempt to clean up outstanding result objects
|
15
|
+
@children = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Boolean] True if our underlying producer object thinks it's
|
19
|
+
# connected to a Kafka broker
|
20
|
+
def connected?
|
21
|
+
return @internal.connected?
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Boolean] True if the underlying producer object has errored
|
25
|
+
def errored?
|
26
|
+
return @internal.errored?
|
27
|
+
end
|
28
|
+
|
29
|
+
def connect(timeout=0)
|
30
|
+
return @internal.connect(timeout * 1000)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Push a value onto the Kafka topic passed to this +Producer+
|
34
|
+
#
|
35
|
+
# @param [Array] value An array of values to push, will push each one
|
36
|
+
# separately
|
37
|
+
# @param [Object] value A single object to push
|
38
|
+
# @return [Hermann::Result] A future-like object which will store the
|
39
|
+
# result from the broker
|
40
|
+
def push(value)
|
41
|
+
result = create_result
|
42
|
+
|
43
|
+
if value.kind_of? Array
|
44
|
+
return value.map { |e| self.push(e) }
|
45
|
+
else
|
46
|
+
@internal.push_single(value, result)
|
47
|
+
end
|
48
|
+
|
49
|
+
return result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a +Hermann::Result+ that is tracked in the Producer's children
|
53
|
+
# array
|
54
|
+
#
|
55
|
+
# @return [Hermann::Result] A new, unused, result
|
56
|
+
def create_result
|
57
|
+
@children << Hermann::Result.new(self)
|
58
|
+
return @children.last
|
59
|
+
end
|
60
|
+
|
61
|
+
# Tick the underlying librdkafka reacter and clean up any unreaped but
|
62
|
+
# reapable children results
|
63
|
+
#
|
64
|
+
# @param [FixNum] timeout Seconds to block on the internal reactor
|
65
|
+
# @return [FixNum] Number of +Hermann::Result+ children reaped
|
66
|
+
def tick_reactor(timeout=0)
|
67
|
+
begin
|
68
|
+
execute_tick(rounded_timeout(timeout))
|
69
|
+
rescue StandardError => ex
|
70
|
+
@children.each do |child|
|
71
|
+
# Skip over any children that should already be reaped for other
|
72
|
+
# reasons
|
73
|
+
next if child.reap?
|
74
|
+
# Propagate errors to the remaining children
|
75
|
+
child.internal_set_error(ex)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Reaping the children at this point will also reap any children marked
|
80
|
+
# as errored by an exception out of #execute_tick
|
81
|
+
return reap_children
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [FixNum] number of children reaped
|
85
|
+
def reap_children
|
86
|
+
# Filter all children who are no longer pending/fulfilled
|
87
|
+
total_children = @children.size
|
88
|
+
@children = @children.reject { |c| c.reap? }
|
89
|
+
|
90
|
+
return (total_children - children.size)
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def rounded_timeout(timeout)
|
97
|
+
# Handle negative numbers, those can be zero
|
98
|
+
return 0 if (timeout < 0)
|
99
|
+
# Since we're going to sleep for each second, round any potential floats
|
100
|
+
# off
|
101
|
+
return timeout.round if timeout.kind_of?(Float)
|
102
|
+
return timeout
|
103
|
+
end
|
104
|
+
|
105
|
+
# Perform the actual reactor tick
|
106
|
+
# @raises [StandardError[ in case of underlying failures in librdkafka
|
107
|
+
def execute_tick(timeout)
|
108
|
+
if timeout == 0
|
109
|
+
@internal.tick(0)
|
110
|
+
else
|
111
|
+
(timeout * 2).times do
|
112
|
+
# We're going to Thread#sleep in Ruby to avoid a
|
113
|
+
# pthread_cond_timedwait(3) inside of librdkafka
|
114
|
+
events = @internal.tick(0)
|
115
|
+
# If we find events, break out early
|
116
|
+
break if events > 0
|
117
|
+
sleep 0.5
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module Hermann
|
3
|
+
class Result
|
4
|
+
attr_reader :reason, :state
|
5
|
+
|
6
|
+
STATES = [:pending,
|
7
|
+
:rejected,
|
8
|
+
:fulfilled,
|
9
|
+
:unfulfilled,
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
def initialize(producer)
|
13
|
+
@producer = producer
|
14
|
+
@reason = nil
|
15
|
+
@value = nil
|
16
|
+
@state = :unfulfilled
|
17
|
+
end
|
18
|
+
|
19
|
+
STATES.each do |state|
|
20
|
+
define_method("#{state}?".to_sym) do
|
21
|
+
return @state == state
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] True if this child can be reaped
|
26
|
+
def reap?
|
27
|
+
return true if rejected? || fulfilled?
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Access the value of the future
|
32
|
+
#
|
33
|
+
# @param [FixNum] timeout Seconds to wait on the underlying machinery for a
|
34
|
+
# result
|
35
|
+
# @return [NilClass] nil if no value could be received in the time alotted
|
36
|
+
# @return [Object]
|
37
|
+
def value(timeout=0)
|
38
|
+
@producer.tick_reactor(timeout)
|
39
|
+
return @value
|
40
|
+
end
|
41
|
+
|
42
|
+
# INTERNAL METHOD ONLY. Do not use
|
43
|
+
#
|
44
|
+
# This method will be invoked by the underlying extension to indicate set
|
45
|
+
# the actual value after a callback has completed
|
46
|
+
#
|
47
|
+
# @param [Object] value The actual resulting value
|
48
|
+
# @param [Boolean] is_error True if the result was errored for whatever
|
49
|
+
# reason
|
50
|
+
def internal_set_value(value, is_error)
|
51
|
+
@value = value
|
52
|
+
|
53
|
+
if is_error
|
54
|
+
puts "Hermann::Result#set_internal_value(#{value.class}:\"#{value}\", error?:#{is_error})"
|
55
|
+
@state = :rejected
|
56
|
+
else
|
57
|
+
@state = :fulfilled
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# INTERNAL METHOD ONLY. Do not use
|
62
|
+
#
|
63
|
+
# This method will set our internal #reason with the details from the
|
64
|
+
# exception
|
65
|
+
#
|
66
|
+
# @param [Exception] exception
|
67
|
+
def internal_set_error(exception)
|
68
|
+
return if exception.nil?
|
69
|
+
|
70
|
+
@reason = exception
|
71
|
+
@state = :rejected
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'system_timer'
|
3
|
+
module Hermann
|
4
|
+
class Timeout
|
5
|
+
USE_SYSTEM_TIMER = true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
rescue LoadError
|
9
|
+
require 'timeout'
|
10
|
+
|
11
|
+
if RUBY_VERSION == '1.8.7'
|
12
|
+
puts ">>> You are running on 1.8.7 without SystemTimer"
|
13
|
+
puts ">>> which means Hermann::Timeout will not work as expected"
|
14
|
+
end
|
15
|
+
module Hermann
|
16
|
+
class Timeout
|
17
|
+
USE_SYSTEM_TIMER = false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Hermann
|
23
|
+
class Timeout
|
24
|
+
def self.system_timer?
|
25
|
+
Hermann::Timeout::USE_SYSTEM_TIMER
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.timeout(seconds, klass=nil, &block)
|
29
|
+
if system_timer?
|
30
|
+
SystemTimer.timeout_after(seconds, klass, &block)
|
31
|
+
else
|
32
|
+
::Timeout.timeout(seconds, klass, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/lib/hermann.rb
ADDED
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hermann
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.16.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stan Campbell
|
8
|
+
- R. Tyler Croy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2014-09-12 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mini_portile
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.6.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *id001
|
25
|
+
description: Ruby gem wrapper for librdkafka
|
26
|
+
email:
|
27
|
+
- stan.campbell3@gmail.com
|
28
|
+
- rtyler.croy@lookout.com
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions:
|
32
|
+
- ext/hermann/extconf.rb
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- Rakefile
|
37
|
+
- ext/hermann/extconf.rb
|
38
|
+
- ext/hermann/hermann_lib.c
|
39
|
+
- ext/hermann/hermann_lib.h
|
40
|
+
- ext/patches/librdkafka/0006-Update-some-headers-to-include-the-right-headers-to-.patch
|
41
|
+
- lib/hermann.rb
|
42
|
+
- lib/hermann/consumer.rb
|
43
|
+
- lib/hermann/errors.rb
|
44
|
+
- lib/hermann/producer.rb
|
45
|
+
- lib/hermann/result.rb
|
46
|
+
- lib/hermann/timeout.rb
|
47
|
+
- lib/hermann/version.rb
|
48
|
+
homepage: https://github.com/lookout/Hermann
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
- ext/hermann
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- &id002
|
62
|
+
- ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- *id002
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.4.1
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: A Kafka consumer/producer gem based on the librdkafka C library.
|
75
|
+
test_files: []
|
76
|
+
|