io_request 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,112 +1,94 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module IORequest
2
- # Message to other side of IO.
4
+ # Single message. Either request or response.
3
5
  class Message
4
- # @return [Integer] ID of message.
5
- attr_reader :id
6
- alias_method :to_i, :id
7
-
8
- # @return [Hash] stored data.
9
- attr_reader :data
6
+ include Utility::WithID
7
+ # Types of messages.
8
+ TYPES = %i[request response].freeze
10
9
 
11
- # Initialize new message.
12
- #
10
+ # Create new message.
13
11
  # @param data [Hash]
14
- # @param id [Integer, nil] if +nil+ provided {Message.generate_id} will be
15
- # used to generate random id.
16
- def initialize(data, id = nil)
17
- @id = id || Message.generate_id
12
+ # @param type [Symbol] one of {TYPES} member.
13
+ # @param id [Utility::ExtendedID, String, nil] only should be filled if message is received from outside.
14
+ # @param to [Utility::ExtendedID, String, nil] if message is response, it should include integer
15
+ # of original request.
16
+ def initialize(data, type: :request, id: nil, to: nil)
18
17
  @data = data
19
- end
18
+ @type = type
19
+ @id = id.nil? ? extended_id : Utility::ExtendedID.from(id)
20
+ @to = to.nil? ? nil : Utility::ExtendedID.from(to)
20
21
 
21
- # @return [String] human-readable form.
22
- def to_s
23
- "#{self.class.name}##{@id}: #{@data.inspect}"
22
+ check_data
24
23
  end
25
24
 
26
- # @return [Integer] random numerical ID based on current time and random salt.
27
- def self.generate_id
28
- ((rand(999) + 1) * Time.now.to_f * 1000).to_i % 2**32
25
+ # Check data correctness.
26
+ def check_data
27
+ raise '@data is not a hash' unless @data.is_a? Hash
28
+ raise 'incorrect @type' unless TYPES.include? @type
29
+ raise 'incorrect @id' unless @id.is_a? Utility::ExtendedID
30
+ raise '@to not specified for response' if response? && @to.nil?
29
31
  end
30
- end
31
-
32
- # Request for server or client.
33
- class Request < Message
34
- # Amount of time to sleep before checking whether responded.
35
- JOIN_SLEEP_TIME = 0.5
36
-
37
- # @return [Integer, Response, nil] ID of response or response itself for this message.
38
- attr_reader :response
39
32
 
40
- # @!visibility private
41
- attr_writer :response
33
+ # @return [Hash]
34
+ attr_reader :data
42
35
 
43
- # Initialize new request.
44
- #
45
- # @param data [Hash]
46
- # @param response [Integer, Response, nil]
47
- # @param id [Integer, nil]
48
- def initialize(data, response = nil, id = nil)
49
- @response = response
50
- super(data, id)
51
- end
36
+ # @return [Symbol]
37
+ attr_reader :type
52
38
 
53
- # @return [String] human readable form.
54
- def to_s
55
- "#{super.to_s}; #{@response ? "Response ID: #{@response.to_i}" : "Not responded"}"
56
- end
39
+ # @return [Utility::ExtendedID]
40
+ attr_reader :id
57
41
 
58
- # Freezes thread until request is responded or until timeout expends.
59
- #
60
- # @param timeout [Integer, Float, nil] timeout size or +nil+ if no timeout.
61
- #
62
- # @return [Integer] amount of time passed
63
- def join(timeout = nil)
64
- time_passed = 0
65
- while @response.nil? && (timeout.nil? || time_passed < timeout)
66
- time_passed += (sleep JOIN_SLEEP_TIME)
67
- end
68
- time_passed
69
- end
42
+ # @return [Utility::ExtendedID]
43
+ attr_reader :to
70
44
 
71
- # Save into hash.
72
- def to_hash
73
- { type: "request", data: @data, id: @id, response: @response.to_i }
45
+ # @return [Boolean]
46
+ def request?
47
+ @type == :request
74
48
  end
75
49
 
76
- # Initialize new request from hash obtained with {Request#to_hash}.
77
- def self.from_hash(hash)
78
- Request.new(hash[:data], hash[:response], hash[:id])
50
+ # @return [Boolean]
51
+ def response?
52
+ @type == :response
79
53
  end
80
- end
81
54
 
82
- # Response to some request.
83
- class Response < Message
84
- # @return [Integer, Request] ID of initial request or request itself.
85
- attr_reader :request
86
-
87
- # Initialize new response.
88
- #
89
- # @param data [Hash]
90
- # @param request [Integer, Request]
91
- # @param id [Integer, nil]
92
- def initialize(data, request, id = nil)
93
- @request = request
94
- super(data, id)
55
+ # @return [String]
56
+ def to_s
57
+ if request?
58
+ "Request##{@id}: #{data}"
59
+ else
60
+ "Response##{@id}: #{data} to ##{@to}"
61
+ end
95
62
  end
96
63
 
97
- # @return [String] human readable form.
98
- def to_s
99
- "#{super.to_s}; Initial request ID: #{@request.to_i}"
64
+ # @return [String] binary data to be passed over IO.
65
+ def to_binary
66
+ json_string = JSON.generate({
67
+ id: @id.to_s,
68
+ type: @type.to_s,
69
+ to: @to.to_s,
70
+ data: @data
71
+ })
72
+ [json_string.size, json_string].pack("Sa#{json_string.size}")
100
73
  end
101
74
 
102
- # Save into hash.
103
- def to_hash
104
- { type: "response", data: @data, id: @id, request: @request.to_i }
75
+ # @param io_w [:write]
76
+ def write_to(io_w)
77
+ io_w.write(to_binary)
105
78
  end
106
79
 
107
- # Initialize new request from hash obtained with {Response#to_hash}.
108
- def self.from_hash(hash)
109
- Response.new(hash[:data], hash[:request], hash[:id])
80
+ # @param io_r [:read]
81
+ # @return [Message]
82
+ def self.read_from(io_r)
83
+ size = io_r.read(2).unpack1('S')
84
+ raise '0 size received' if size.zero?
85
+
86
+ json_string = io_r.read(size).unpack1("a#{size}")
87
+ msg = JSON.parse(json_string, symbolize_names: true)
88
+ Message.new(msg[:data],
89
+ id: msg[:id],
90
+ type: msg[:type].to_sym,
91
+ to: msg[:to])
110
92
  end
111
93
  end
112
94
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IORequest
4
+ # Utility methods.
5
+ module Utility
6
+ # Adds some methods to spawn new threads and join them.
7
+ # @note This module creates instance variables with prefix +@__multi_thread__+.
8
+ module MultiThread
9
+ private
10
+
11
+ # @return [Array<Thread>] array of running threads.
12
+ def __multi_thread__threads
13
+ @__multi_thread__threads ||= []
14
+ end
15
+ alias running_threads __multi_thread__threads
16
+
17
+ # @return [Mutex] threads manipulations mutex.
18
+ def __multi_thread__mutex
19
+ @__multi_thread__mutex ||= Mutex.new
20
+ end
21
+
22
+ # Runs block with provided arguments forwarded as arguments in separate thread.
23
+ # All the inline args will be passed to block.
24
+ # @param thread_name [String] thread name.
25
+ # @return [Thread]
26
+ def in_thread(*args, name: nil)
27
+ # Synchronizing addition/deletion of new threads. That's important
28
+ __multi_thread__mutex.synchronize do
29
+ new_thread = Thread.new(*args) do |*in_args|
30
+ yield(*in_args)
31
+ ensure
32
+ __multi_thread__remove_current_thread
33
+ end
34
+ __multi_thread__threads << new_thread
35
+ new_thread.name = name if name
36
+ new_thread
37
+ end
38
+ end
39
+
40
+ # Removes current thread from thread list.
41
+ def __multi_thread__remove_current_thread
42
+ __multi_thread__mutex.synchronize do
43
+ __multi_thread__threads.delete(Thread.current)
44
+ end
45
+ end
46
+
47
+ # For each running thread.
48
+ def each_thread(&block)
49
+ __multi_thread__threads.each(&block)
50
+ end
51
+
52
+ # Kills each thread.
53
+ def kill_threads
54
+ each_thread(&:kill)
55
+ each_thread(&:join)
56
+ end
57
+
58
+ # Joins each thread.
59
+ def join_threads
60
+ each_thread(&:join)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IORequest
4
+ # Utility methods.
5
+ module Utility
6
+ # Extended Id of object
7
+ class ExtendedID
8
+ include Comparable
9
+
10
+ # Create new Id based on PID, thread ID and object ID.
11
+ def initialize(pid = nil, tid = nil, oid = nil)
12
+ @pid = pid || Process.pid
13
+ @tid = tid || Thread.current.object_id
14
+ @oid = oid || object_id
15
+ end
16
+
17
+ # @return [Integer] process ID.
18
+ attr_reader :pid
19
+
20
+ # @return [Integer] thread ID.
21
+ attr_reader :tid
22
+
23
+ # @return [Integer] object ID.
24
+ attr_reader :oid
25
+
26
+ # @return [String]
27
+ def to_s
28
+ "#{@pid}##{@tid}##{@oid}"
29
+ end
30
+
31
+ # Comparison operator.
32
+ def <=>(other)
33
+ if @pid == other.pid && @tid == other.tid
34
+ @oid <=> other.oid
35
+ elsif @pid == other.pid && @tid != other.tid
36
+ @tid <=> tid
37
+ else
38
+ @pid <=> other.pid
39
+ end
40
+ end
41
+
42
+ def self.from(obj)
43
+ case obj
44
+ when ExtendedID then new(obj.pid, obj.tid, obj.oid)
45
+ when String then new(*obj.split('#').map(&:to_i))
46
+ else
47
+ raise 'unknown type'
48
+ end
49
+ end
50
+ end
51
+ # Adds special method to return object ID.
52
+ module WithID
53
+ # Identifies object in thread and process.
54
+ def __with_id__extended_id
55
+ @__with_id__extended_id ||= ExtendedID.new
56
+ end
57
+ alias extended_id __with_id__extended_id
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IORequest
4
+ # Utility methods.
5
+ module Utility
6
+ # Adds special method to identify object in log files.
7
+ module WithProgName
8
+ # Identifies object and thread it runs in.
9
+ def prog_name
10
+ "#{self.class.name}##{object_id} in Thread##{Thread.current.object_id}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module IORequest
2
4
  # Gem version.
3
- VERSION = "1.2.0"
5
+ VERSION = '2.0.0'
4
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io_request
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fizvlad
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-04 00:00:00.000000000 Z
11
+ date: 2020-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,75 +25,75 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '13.0'
33
+ version: '5.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '13.0'
40
+ version: '5.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: minitest
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
47
+ version: '13.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.0'
54
+ version: '13.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: logger
56
+ name: json
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.4'
61
+ version: '2.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.4'
68
+ version: '2.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: timeout-extensions
70
+ name: logger
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.1.1
75
+ version: '1.4'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.1.1
82
+ version: '1.4'
83
83
  - !ruby/object:Gem::Dependency
84
- name: json
84
+ name: timeout-extensions
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '2.0'
89
+ version: 0.1.1
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '2.0'
96
+ version: 0.1.1
97
97
  description:
98
98
  email:
99
99
  - fizvlad@mail.ru
@@ -102,9 +102,10 @@ extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
104
  - ".gitignore"
105
+ - ".rubocop.yml"
106
+ - ".rubocop_todo.yml"
105
107
  - ".travis.yml"
106
108
  - Gemfile
107
- - Gemfile.lock
108
109
  - LICENSE.txt
109
110
  - README.md
110
111
  - Rakefile
@@ -112,10 +113,12 @@ files:
112
113
  - examples/simple_example.rb
113
114
  - io_request.gemspec
114
115
  - lib/io_request.rb
116
+ - lib/io_request/authorizer.rb
115
117
  - lib/io_request/client.rb
116
- - lib/io_request/logging.rb
117
118
  - lib/io_request/message.rb
118
- - lib/io_request/utility.rb
119
+ - lib/io_request/utility/multi_thread.rb
120
+ - lib/io_request/utility/with_id.rb
121
+ - lib/io_request/utility/with_prog_name.rb
119
122
  - lib/io_request/version.rb
120
123
  homepage: https://github.com/fizvlad/io-request-rb
121
124
  licenses:
@@ -132,14 +135,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
135
  requirements:
133
136
  - - ">="
134
137
  - !ruby/object:Gem::Version
135
- version: 2.3.1
138
+ version: 2.6.5
136
139
  required_rubygems_version: !ruby/object:Gem::Requirement
137
140
  requirements:
138
141
  - - ">="
139
142
  - !ruby/object:Gem::Version
140
143
  version: '0'
141
144
  requirements: []
142
- rubygems_version: 3.0.4
145
+ rubygems_version: 3.1.0.pre2
143
146
  signing_key:
144
147
  specification_version: 4
145
148
  summary: Small gem to create JSON request/response type of connection over IO object