zold 0.15.0 → 0.16.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/bin/zold +9 -6
  4. data/features/gem_package.feature +1 -1
  5. data/features/step_definitions/steps.rb +2 -2
  6. data/fixtures/merge/into-no-wallet/assert.rb +1 -1
  7. data/fixtures/merge/random-expenses/assert.rb +1 -1
  8. data/fixtures/merge/simple-case/assert.rb +1 -1
  9. data/lib/zold/age.rb +16 -8
  10. data/lib/zold/amount.rb +4 -2
  11. data/lib/zold/cached_wallets.rb +5 -6
  12. data/lib/zold/commands/clean.rb +15 -8
  13. data/lib/zold/commands/diff.rb +3 -3
  14. data/lib/zold/commands/fetch.rb +4 -4
  15. data/lib/zold/commands/list.rb +5 -4
  16. data/lib/zold/commands/merge.rb +2 -1
  17. data/lib/zold/commands/node.rb +14 -12
  18. data/lib/zold/commands/propagate.rb +2 -2
  19. data/lib/zold/commands/push.rb +3 -3
  20. data/lib/zold/commands/remove.rb +4 -1
  21. data/lib/zold/commands/routines/reconnect.rb +1 -0
  22. data/lib/zold/copies.rb +13 -14
  23. data/lib/zold/dir_items.rb +44 -0
  24. data/lib/zold/head.rb +1 -1
  25. data/lib/zold/key.rb +2 -2
  26. data/lib/zold/log.rb +2 -0
  27. data/lib/zold/node/async_entrance.rb +38 -28
  28. data/lib/zold/node/entrance.rb +4 -11
  29. data/lib/zold/node/farm.rb +9 -9
  30. data/lib/zold/node/front.rb +40 -25
  31. data/lib/zold/node/nodup_entrance.rb +4 -4
  32. data/lib/zold/node/safe_entrance.rb +2 -2
  33. data/lib/zold/node/spread_entrance.rb +5 -9
  34. data/lib/zold/node/sync_entrance.rb +2 -23
  35. data/lib/zold/patch.rb +2 -2
  36. data/lib/zold/remotes.rb +10 -6
  37. data/lib/zold/sync_wallets.rb +3 -22
  38. data/lib/zold/tree_wallets.rb +11 -6
  39. data/lib/zold/txns.rb +1 -1
  40. data/lib/zold/version.rb +1 -1
  41. data/lib/zold/wallet.rb +14 -5
  42. data/lib/zold/wallets.rb +5 -4
  43. data/test/commands/routines/test_spread.rb +1 -1
  44. data/test/commands/test_alias.rb +4 -4
  45. data/test/commands/test_clean.rb +14 -1
  46. data/test/commands/test_create.rb +2 -2
  47. data/test/commands/test_diff.rb +5 -5
  48. data/test/commands/test_fetch.rb +2 -2
  49. data/test/commands/test_merge.rb +19 -19
  50. data/test/commands/test_node.rb +1 -1
  51. data/test/commands/test_pay.rb +7 -6
  52. data/test/commands/test_propagate.rb +2 -1
  53. data/test/commands/test_pull.rb +1 -1
  54. data/test/commands/test_push.rb +1 -1
  55. data/test/commands/test_remove.rb +57 -0
  56. data/test/commands/test_taxes.rb +1 -1
  57. data/test/fake_home.rb +11 -8
  58. data/test/node/fake_node.rb +2 -2
  59. data/test/node/test_async_entrance.rb +24 -8
  60. data/test/node/test_emission.rb +2 -2
  61. data/test/node/test_entrance.rb +8 -6
  62. data/test/node/test_farm.rb +1 -1
  63. data/test/node/test_front.rb +42 -33
  64. data/test/node/test_nodup_entrance.rb +2 -2
  65. data/test/node/test_safe_entrance.rb +5 -5
  66. data/test/node/test_spread_entrance.rb +3 -3
  67. data/test/node/test_sync_entrance.rb +1 -1
  68. data/test/test__helper.rb +3 -29
  69. data/test/test_cached_wallets.rb +1 -1
  70. data/test/test_copies.rb +4 -2
  71. data/test/test_dir_items.rb +88 -0
  72. data/test/test_key.rb +2 -2
  73. data/test/test_log.rb +38 -0
  74. data/test/test_patch.rb +11 -11
  75. data/test/test_prefixes.rb +1 -1
  76. data/test/test_remotes.rb +12 -6
  77. data/test/test_sync_wallets.rb +6 -6
  78. data/test/test_tax.rb +4 -4
  79. data/test/test_tree_wallets.rb +16 -2
  80. data/test/test_wallet.rb +26 -26
  81. data/test/test_wallets.rb +5 -2
  82. data/test/test_zold.rb +2 -2
  83. data/test/upgrades/test_protocol_up.rb +1 -1
  84. data/upgrades/move_wallets_into_tree.rb +1 -1
  85. data/upgrades/protocol_up.rb +3 -3
  86. data/upgrades/rename_foreign_wallets.rb +1 -1
  87. data/zold.gemspec +27 -25
  88. metadata +91 -56
@@ -47,6 +47,7 @@ module Zold
47
47
  score = @farm.best[0]
48
48
  args << "--ignore-node=#{score.host}:#{score.port}" if score
49
49
  cmd.run(args + ['defaults']) unless @opts['routine-immediately']
50
+ return if @opts['routine-immediately'] && @remotes.all.empty?
50
51
  cmd.run(args + ['trim'])
51
52
  cmd.run(args + ['select'])
52
53
  cmd.run(args + ['update'] + (@opts['never-reboot'] ? [] : ['--reboot']))
data/lib/zold/copies.rb CHANGED
@@ -22,10 +22,12 @@
22
22
 
23
23
  require 'time'
24
24
  require 'csv'
25
+ require 'futex'
25
26
  require 'backtrace'
26
27
  require_relative 'log'
27
28
  require_relative 'size'
28
29
  require_relative 'wallet'
30
+ require_relative 'dir_items'
29
31
 
30
32
  # The list of copies.
31
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -38,15 +40,12 @@ module Zold
38
40
  EXT = '.zc'
39
41
 
40
42
  def initialize(dir, log: Log::Quiet.new)
41
- raise 'Dir can\'t be nil' if dir.nil?
42
43
  @dir = dir
43
- raise 'Log can\'t be nil' if log.nil?
44
44
  @log = log
45
- @mutex = Mutex.new
46
45
  end
47
46
 
48
47
  def root
49
- File.join(@dir, '..')
48
+ File.expand_path(File.join(@dir, '..'))
50
49
  end
51
50
 
52
51
  def to_s
@@ -54,7 +53,7 @@ module Zold
54
53
  end
55
54
 
56
55
  def clean
57
- @mutex.synchronize do
56
+ Futex.new(file, log: @log).open do
58
57
  list = load
59
58
  list.reject! { |s| s[:time] < Time.now - 24 * 60 * 60 }
60
59
  save(list)
@@ -74,7 +73,7 @@ module Zold
74
73
  wallet.refurbish
75
74
  raise "Invalid protocol #{wallet.protocol} in #{file}" unless wallet.protocol == Zold::PROTOCOL
76
75
  rescue StandardError => e
77
- File.delete(file)
76
+ FileUtils.rm_rf(file)
78
77
  @log.debug("Copy at #{f} deleted: #{Backtrace.new(e)}")
79
78
  deleted += 1
80
79
  end
@@ -84,7 +83,7 @@ module Zold
84
83
  end
85
84
 
86
85
  def remove(host, port)
87
- @mutex.synchronize do
86
+ Futex.new(file, log: @log).open do
88
87
  save(load.reject { |s| s[:host] == host && s[:port] == port })
89
88
  end
90
89
  end
@@ -98,21 +97,21 @@ module Zold
98
97
  raise "Time must be in the past: #{time}" if time > Time.now
99
98
  raise 'Score must be Integer' unless score.is_a?(Integer)
100
99
  raise "Score can't be negative: #{score}" if score.negative?
101
- @mutex.synchronize do
100
+ Futex.new(file, log: @log).open do
102
101
  FileUtils.mkdir_p(@dir)
103
102
  list = load
104
103
  target = list.find do |s|
105
104
  f = File.join(@dir, "#{s[:name]}#{Copies::EXT}")
106
- File.exist?(f) && File.read(f) == content
105
+ File.exist?(f) && IO.read(f) == content
107
106
  end
108
107
  if target.nil?
109
- max = Dir.new(@dir)
108
+ max = DirItems.new(@dir).fetch
110
109
  .select { |f| File.basename(f, Copies::EXT) =~ /^[0-9]+$/ }
111
110
  .map(&:to_i)
112
111
  .max
113
112
  max = 0 if max.nil?
114
113
  name = (max + 1).to_s
115
- File.write(File.join(@dir, "#{name}#{Copies::EXT}"), content)
114
+ IO.write(File.join(@dir, "#{name}#{Copies::EXT}"), content)
116
115
  else
117
116
  name = target[:name]
118
117
  end
@@ -130,7 +129,7 @@ module Zold
130
129
  end
131
130
 
132
131
  def all
133
- @mutex.synchronize do
132
+ Futex.new(file, log: @log).open do
134
133
  load.group_by { |s| s[:name] }.map do |name, scores|
135
134
  {
136
135
  name: name,
@@ -160,7 +159,7 @@ module Zold
160
159
  private
161
160
 
162
161
  def save(list)
163
- File.write(
162
+ IO.write(
164
163
  file,
165
164
  list.map do |r|
166
165
  [
@@ -173,7 +172,7 @@ module Zold
173
172
  end
174
173
 
175
174
  def files
176
- Dir.new(@dir).select { |f| File.basename(f, Copies::EXT) =~ /^[0-9]+$/ }
175
+ DirItems.new(@dir).fetch.select { |f| File.basename(f, Copies::EXT) =~ /^[0-9]+$/ }
177
176
  end
178
177
 
179
178
  def file
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ # Items in a directory.
24
+ #
25
+ # We need this class because Dir.new() from Ruby is blocking. It doesn't
26
+ # allow to write and read to any files in a directory, while listing it.
27
+ # More: https://stackoverflow.com/questions/52987672/
28
+ #
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
31
+ # License:: MIT
32
+ module Zold
33
+ # Items in a dir
34
+ class DirItems
35
+ def initialize(dir)
36
+ @dir = dir
37
+ end
38
+
39
+ def fetch(recursive: true)
40
+ txt = `find #{@dir} #{recursive ? '' : '-maxdepth 1'} -type f -print 2>/dev/null`
41
+ txt.nil? ? [] : txt.strip.split(' ').map { |f| f[(@dir.length + 1)..-1] }
42
+ end
43
+ end
44
+ end
data/lib/zold/head.rb CHANGED
@@ -39,7 +39,7 @@ module Zold
39
39
 
40
40
  def fetch
41
41
  raise "Wallet file '#{@file}' is absent" unless File.exist?(@file)
42
- lines = File.read(@file).split(/\n/)
42
+ lines = IO.read(@file).split(/\n/)
43
43
  raise "Not enough lines in #{@file}, just #{lines.count}" if lines.count < 4
44
44
  lines.take(4)
45
45
  end
data/lib/zold/key.rb CHANGED
@@ -37,7 +37,7 @@ module Zold
37
37
  unless file.nil?
38
38
  path = File.expand_path(file)
39
39
  raise "Can't find RSA key at #{file} (#{path})" unless File.exist?(path)
40
- return File.read(path)
40
+ return IO.read(path)
41
41
  end
42
42
  unless text.nil?
43
43
  return text if text.start_with?('-----')
@@ -77,7 +77,7 @@ module Zold
77
77
  text = @body.call.strip
78
78
  unless text.start_with?('-----BEGIN')
79
79
  Tempfile.open do |f|
80
- File.write(f.path, text)
80
+ IO.write(f.path, text)
81
81
  text = `ssh-keygen -f #{f.path} -e -m pem`
82
82
  end
83
83
  end
data/lib/zold/log.rb CHANGED
@@ -54,6 +54,7 @@ module Zold
54
54
  end
55
55
 
56
56
  def debug(msg)
57
+ return unless debug?
57
58
  @mutex.synchronize do
58
59
  @log.debug(msg)
59
60
  end
@@ -64,6 +65,7 @@ module Zold
64
65
  end
65
66
 
66
67
  def info(msg)
68
+ return unless info?
67
69
  @mutex.synchronize do
68
70
  @log.info(msg)
69
71
  end
@@ -21,11 +21,13 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'concurrent'
24
+ require 'futex'
24
25
  require_relative '../log'
25
26
  require_relative '../age'
26
27
  require_relative '../size'
27
28
  require_relative '../id'
28
29
  require_relative '../verbose_thread'
30
+ require_relative '../dir_items'
29
31
 
30
32
  # The async entrance of the web front.
31
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -35,29 +37,25 @@ module Zold
35
37
  # The entrance
36
38
  class AsyncEntrance
37
39
  # How many threads to use for processing
38
- THREADS = Concurrent.processor_count * 8
40
+ THREADS = [Concurrent.processor_count, 4].max
39
41
 
40
42
  # Queue length
41
43
  MAX_QUEUE = Concurrent.processor_count * 64
42
44
 
43
45
  def initialize(entrance, dir, log: Log::Quiet.new)
44
- raise 'Entrance can\'t be nil' if entrance.nil?
45
46
  @entrance = entrance
46
- raise 'Directory can\'t be nil' if dir.nil?
47
- raise 'Directory must be of type String' unless dir.is_a?(String)
48
47
  @dir = dir
49
- raise 'Log can\'t be nil' if log.nil?
50
48
  @log = log
51
49
  @mutex = Mutex.new
52
50
  end
53
51
 
54
52
  def to_json
53
+ opts = queue
55
54
  json = {
56
- 'queue': queue.count,
55
+ 'queue': opts.count,
57
56
  'pool.length': @pool.length,
58
57
  'pool.running': @pool.running?
59
58
  }
60
- opts = queue
61
59
  json['queue_age'] = opts.empty? ? 0 : Time.now - File.mtime(File.join(@dir, opts[0]))
62
60
  @entrance.to_json.merge(json)
63
61
  end
@@ -66,7 +64,9 @@ module Zold
66
64
  @entrance.start do
67
65
  FileUtils.mkdir_p(@dir)
68
66
  @pool = Concurrent::FixedThreadPool.new(
69
- AsyncEntrance::THREADS, max_queue: AsyncEntrance::MAX_QUEUE, fallback_policy: :abort
67
+ AsyncEntrance::THREADS,
68
+ max_queue: AsyncEntrance::MAX_QUEUE,
69
+ fallback_policy: :abort
70
70
  )
71
71
  AsyncEntrance::THREADS.times do |t|
72
72
  @pool.post do
@@ -80,14 +80,21 @@ module Zold
80
80
  end
81
81
  begin
82
82
  yield(self)
83
+ cycle = 0
84
+ until queue.empty?
85
+ @log.info("Stopping async entrance, #{queue.count} still in the queue (cycle=#{cycle})...")
86
+ cycle += 1
87
+ raise "Can't wait for async entrance to stop for so long" if cycle > 10
88
+ sleep 1
89
+ end
83
90
  ensure
84
91
  @log.info("Stopping async entrance, pool length is #{@pool.length}, queue length is #{@pool.queue_length}")
85
92
  @pool.shutdown
86
93
  if @pool.wait_for_termination(10)
87
- @log.info('Async entrance terminated peacefully')
94
+ @log.info("Async entrance terminated peacefully with #{queue.count} wallets left in the queue")
88
95
  else
89
96
  @pool.kill
90
- @log.info('Async entrance was killed')
97
+ @log.info("Async entrance was killed, #{queue.count} wallets left in the queue")
91
98
  end
92
99
  end
93
100
  end
@@ -96,36 +103,39 @@ module Zold
96
103
  # Always returns an array with a single ID of the pushed wallet
97
104
  def push(id, body)
98
105
  raise "Queue is too long (#{queue.count} wallets), try again later" if queue.count > AsyncEntrance::MAX_QUEUE
99
- @mutex.synchronize do
100
- File.write(File.join(@dir, id.to_s), body)
106
+ start = Time.now
107
+ Futex.new(file(id), log: @log).open do |f|
108
+ IO.write(f, body)
101
109
  end
110
+ @log.debug("Added #{id}/#{Size.new(body.length)} to the queue at pos.#{queue.count} \
111
+ in #{Age.new(start, limit: 0.05)}")
102
112
  [id]
103
113
  end
104
114
 
105
115
  private
106
116
 
107
117
  def take
108
- id = ''
109
- body = ''
110
- @mutex.synchronize do
111
- opts = queue
112
- unless opts.empty?
113
- file = File.join(@dir, opts[0])
114
- id = opts[0]
115
- body = File.read(file)
116
- File.delete(file)
117
- end
118
- end
119
- return if id.empty? || body.empty?
120
118
  start = Time.now
119
+ opts = queue
120
+ return if opts.empty?
121
+ id = opts[0]
122
+ body = Futex.new(file(id), log: @log).open do |f|
123
+ b = File.exist?(f) ? IO.read(f) : ''
124
+ FileUtils.rm_f(f)
125
+ b
126
+ end
127
+ return if body.empty?
121
128
  @entrance.push(Id.new(id), body)
122
- @log.debug("Pushed #{id}/#{Size.new(body.length)} to #{@entrance.class.name} in #{Age.new(start)}")
129
+ @log.debug("Pushed #{id}/#{Size.new(body.length)} to #{@entrance.class.name} \
130
+ in #{Age.new(start, limit: 0.1)} (#{queue.count} still in the queue)")
123
131
  end
124
132
 
125
133
  def queue
126
- Dir.new(@dir)
127
- .select { |f| f =~ /^[0-9a-f]{16}$/ }
128
- .sort_by { |f| File.mtime(File.join(@dir, f)) }
134
+ DirItems.new(@dir).fetch.select { |f| f =~ /^[0-9a-f]{16}$/ }
135
+ end
136
+
137
+ def file(id)
138
+ File.join(@dir, id.to_s)
129
139
  end
130
140
  end
131
141
  end
@@ -25,6 +25,7 @@ require_relative '../log'
25
25
  require_relative '../remotes'
26
26
  require_relative '../copies'
27
27
  require_relative '../tax'
28
+ require_relative '../age'
28
29
  require_relative '../commands/clean'
29
30
  require_relative '../commands/merge'
30
31
  require_relative '../commands/fetch'
@@ -38,19 +39,11 @@ module Zold
38
39
  # The entrance
39
40
  class Entrance
40
41
  def initialize(wallets, remotes, copies, address, log: Log::Quiet.new, network: 'test')
41
- raise 'Wallets can\'t be nil' if wallets.nil?
42
- raise 'Wallets must implement the contract of Wallets: method #find is required' unless wallets.respond_to?(:find)
43
42
  @wallets = wallets
44
- raise 'Remotes can\'t be nil' if remotes.nil?
45
- raise "Remotes must be of type Remotes: #{remotes.class.name}" unless remotes.is_a?(Remotes)
46
43
  @remotes = remotes
47
- raise 'Copies can\'t be nil' if copies.nil?
48
44
  @copies = copies
49
- raise 'Address can\'t be nil' if address.nil?
50
45
  @address = address
51
- raise 'Log can\'t be nil' if log.nil?
52
46
  @log = log
53
- raise 'Network can\'t be nil' if network.nil?
54
47
  @network = network
55
48
  @history = []
56
49
  @speed = []
@@ -88,12 +81,12 @@ module Zold
88
81
  ).run(['merge', id.to_s])
89
82
  Clean.new(wallets: @wallets, copies: copies.root, log: @log).run(['clean', id.to_s])
90
83
  copies.remove(localhost, Remotes::PORT)
91
- sec = (Time.now - start).round(2)
92
84
  if modified.empty?
93
- @log.info("Accepted #{id} in #{sec}s and not modified anything")
85
+ @log.info("Accepted #{id} in #{Age.new(start, limit: 1)} and not modified anything")
94
86
  else
95
- @log.info("Accepted #{id} in #{sec}s and modified #{modified.join(', ')}")
87
+ @log.info("Accepted #{id} in #{Age.new(start, limit: 1)} and modified #{modified.join(', ')}")
96
88
  end
89
+ sec = (Time.now - start).round(2)
97
90
  @mutex.synchronize do
98
91
  @history.shift if @history.length >= 16
99
92
  @speed.shift if @speed.length >= 64
@@ -23,6 +23,7 @@
23
23
  require 'time'
24
24
  require 'open3'
25
25
  require 'backtrace'
26
+ require 'futex'
26
27
  require_relative '../log'
27
28
  require_relative '../score'
28
29
  require_relative '../age'
@@ -48,7 +49,6 @@ module Zold
48
49
  @invoice = invoice
49
50
  @pipeline = Queue.new
50
51
  @threads = []
51
- @mutex = Mutex.new
52
52
  end
53
53
 
54
54
  def best
@@ -127,14 +127,14 @@ module Zold
127
127
  def finish(thread)
128
128
  start = Time.now
129
129
  @alive = false
130
- @log.info("Attempting to terminate the thread \"#{thread.name}\"...")
130
+ @log.info("Attempting to terminate the thread \"#{thread.name}\" of the farm...")
131
131
  loop do
132
132
  delay = Time.now - start
133
133
  if thread.join(0.1)
134
134
  @log.info("Thread \"#{thread.name}\" finished in #{Age.new(start)}")
135
135
  break
136
136
  end
137
- if delay > 10
137
+ if delay > 1
138
138
  thread.exit
139
139
  @log.error("Thread \"#{thread.name}\" forcefully terminated after #{Age.new(start)}")
140
140
  end
@@ -220,9 +220,9 @@ module Zold
220
220
  def save(threads, list = [])
221
221
  scores = load + list
222
222
  period = 24 * 60 * 60 / [threads, 1].max
223
- @mutex.synchronize do
224
- File.write(
225
- @cache,
223
+ Futex.new(@cache, log: @log).open do |f|
224
+ IO.write(
225
+ f,
226
226
  scores.select(&:valid?)
227
227
  .reject(&:expired?)
228
228
  .sort_by(&:value)
@@ -237,9 +237,9 @@ module Zold
237
237
  end
238
238
 
239
239
  def load
240
- @mutex.synchronize do
241
- if File.exist?(@cache)
242
- File.read(@cache).split(/\n/)
240
+ Futex.new(@cache, log: @log).open do |f|
241
+ if File.exist?(f)
242
+ IO.read(f).split(/\n/)
243
243
  .map { |t| parse_score_line(t) }
244
244
  .reject(&:zero?)
245
245
  else
@@ -55,7 +55,7 @@ module Zold
55
55
  set :suppress_messages, true
56
56
  set :start, Time.now
57
57
  set :lock, false
58
- set :show_exceptions, false
58
+ set :show_exceptions, true
59
59
  set :server, :puma
60
60
  set :log, nil? # to be injected at node.rb
61
61
  set :trace, nil? # to be injected at node.rb
@@ -68,6 +68,7 @@ module Zold
68
68
  set :nohup_log, false # to be injected at node.rb
69
69
  set :home, nil? # to be injected at node.rb
70
70
  set :logging, true # to be injected at node.rb
71
+ set :logger, nil? # to be injected at node.rb
71
72
  set :address, nil? # to be injected at node.rb
72
73
  set :farm, nil? # to be injected at node.rb
73
74
  set :metronome, nil? # to be injected at node.rb
@@ -121,7 +122,7 @@ while #{settings.address} is in '#{settings.network}'"
121
122
  headers[Http::PROTOCOL_HEADER] = settings.protocol.to_s
122
123
  headers['Access-Control-Allow-Origin'] = '*'
123
124
  headers[Http::SCORE_HEADER] = score.reduced(16).to_s
124
- headers['X-Zold-Thread'] = Thread.current.name
125
+ headers['X-Zold-Thread'] = Thread.current.name.to_s
125
126
  headers['X-Zold-Milliseconds'] = ((Time.now - @start) * 1000).round.to_s
126
127
  end
127
128
 
@@ -155,7 +156,7 @@ while #{settings.address} is in '#{settings.network}'"
155
156
  raise "Log not found at #{settings.nohup_log}" unless File.exist?(settings.nohup_log)
156
157
  response.headers['Content-Type'] = 'text/plain'
157
158
  response.headers['Content-Disposition'] = "attachment; filename='#{File.basename(settings.nohup_log)}'"
158
- File.read(settings.nohup_log)
159
+ IO.read(settings.nohup_log)
159
160
  end
160
161
 
161
162
  get '/favicon.ico' do
@@ -177,12 +178,12 @@ while #{settings.address} is in '#{settings.network}'"
177
178
  protocol: settings.protocol,
178
179
  score: score.to_h,
179
180
  pid: Process.pid,
180
- cpus: Concurrent.processor_count,
181
+ cpus: Cachy.cache(:a_cpus) { Concurrent.processor_count },
181
182
  memory: GetProcessMem.new.bytes.to_i,
182
183
  platform: RUBY_PLATFORM,
183
- load: Usagewatch.uw_load.to_f,
184
+ load: Cachy.cache(:a_load, expires_in: 5 * 60) { Usagewatch.uw_load.to_f },
184
185
  threads: "#{Thread.list.select { |t| t.status == 'run' }.count}/#{Thread.list.count}",
185
- wallets: Cachy.cache(:a_key, expires_in: 5 * 60) { settings.wallets.all.count },
186
+ wallets: Cachy.cache(:a_wallets, expires_in: 5 * 60) { settings.wallets.all.count },
186
187
  remotes: settings.remotes.all.count,
187
188
  nscore: settings.remotes.all.map { |r| r[:score] }.inject(&:+) || 0,
188
189
  farm: settings.farm.to_json,
@@ -196,7 +197,7 @@ while #{settings.address} is in '#{settings.network}'"
196
197
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})} do
197
198
  error 404 if settings.disable_fetch
198
199
  id = Id.new(params[:id])
199
- settings.wallets.find(id) do |wallet|
200
+ copy_of(id) do |wallet|
200
201
  error 404 unless wallet.exists?
201
202
  content_type 'application/json'
202
203
  JSON.pretty_generate(
@@ -219,7 +220,7 @@ while #{settings.address} is in '#{settings.network}'"
219
220
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16}).json} do
220
221
  error 404 if settings.disable_fetch
221
222
  id = Id.new(params[:id])
222
- settings.wallets.find(id) do |wallet|
223
+ copy_of(id) do |wallet|
223
224
  error 404 unless wallet.exists?
224
225
  content_type 'application/json'
225
226
  JSON.pretty_generate(
@@ -241,7 +242,7 @@ while #{settings.address} is in '#{settings.network}'"
241
242
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/balance} do
242
243
  error 404 if settings.disable_fetch
243
244
  id = Id.new(params[:id])
244
- settings.wallets.find(id) do |wallet|
245
+ copy_of(id) do |wallet|
245
246
  error 404 unless wallet.exists?
246
247
  content_type 'text/plain'
247
248
  wallet.balance.to_i.to_s
@@ -251,7 +252,7 @@ while #{settings.address} is in '#{settings.network}'"
251
252
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/key} do
252
253
  error 404 if settings.disable_fetch
253
254
  id = Id.new(params[:id])
254
- settings.wallets.find(id) do |wallet|
255
+ copy_of(id) do |wallet|
255
256
  error 404 unless wallet.exists?
256
257
  content_type 'text/plain'
257
258
  wallet.key.to_pub
@@ -261,7 +262,7 @@ while #{settings.address} is in '#{settings.network}'"
261
262
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/mtime} do
262
263
  error 404 if settings.disable_fetch
263
264
  id = Id.new(params[:id])
264
- settings.wallets.find(id) do |wallet|
265
+ copy_of(id) do |wallet|
265
266
  error 404 unless wallet.exists?
266
267
  content_type 'text/plain'
267
268
  wallet.mtime.utc.iso8601.to_s
@@ -271,7 +272,7 @@ while #{settings.address} is in '#{settings.network}'"
271
272
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/digest} do
272
273
  error 404 if settings.disable_fetch
273
274
  id = Id.new(params[:id])
274
- settings.wallets.find(id) do |wallet|
275
+ copy_of(id) do |wallet|
275
276
  error 404 unless wallet.exists?
276
277
  content_type 'text/plain'
277
278
  wallet.digest
@@ -281,7 +282,7 @@ while #{settings.address} is in '#{settings.network}'"
281
282
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.txt} do
282
283
  error 404 if settings.disable_fetch
283
284
  id = Id.new(params[:id])
284
- settings.wallets.find(id) do |wallet|
285
+ copy_of(id) do |wallet|
285
286
  error 404 unless wallet.exists?
286
287
  content_type 'text/plain'
287
288
  [
@@ -305,17 +306,17 @@ while #{settings.address} is in '#{settings.network}'"
305
306
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.bin} do
306
307
  error 404 if settings.disable_fetch
307
308
  id = Id.new(params[:id])
308
- settings.wallets.find(id) do |wallet|
309
+ copy_of(id) do |wallet|
309
310
  error 404 unless wallet.exists?
310
311
  content_type 'text/plain'
311
- File.read(wallet.path)
312
+ IO.read(wallet.path)
312
313
  end
313
314
  end
314
315
 
315
316
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/copies} do
316
317
  error 404 if settings.disable_fetch
317
318
  id = Id.new(params[:id])
318
- settings.wallets.find(id) do |wallet|
319
+ copy_of(id) do |wallet|
319
320
  error 404 unless wallet.exists?
320
321
  content_type 'text/plain'
321
322
  copies = Copies.new(File.join(settings.copies, id))
@@ -335,12 +336,12 @@ while #{settings.address} is in '#{settings.network}'"
335
336
  error 404 if settings.disable_fetch
336
337
  id = Id.new(params[:id])
337
338
  name = params[:name]
338
- settings.wallets.find(id) do |wallet|
339
+ copy_of(id) do |wallet|
339
340
  error 404 unless wallet.exists?
340
341
  copy = Copies.new(File.join(settings.copies, id)).all.find { |c| c[:name] == name }
341
342
  error 404 if copy.nil?
342
343
  content_type 'text/plain'
343
- File.read(copy[:path])
344
+ IO.read(copy[:path])
344
345
  end
345
346
  end
346
347
 
@@ -383,12 +384,15 @@ while #{settings.address} is in '#{settings.network}'"
383
384
 
384
385
  get '/threads' do
385
386
  content_type 'text/plain'
386
- Thread.list.map do |t|
387
- [
388
- "#{t.name}: status=#{t.status}; alive=#{t.alive?}",
389
- t.backtrace.nil? ? 'NO BACKTRACE' : " #{t.backtrace.join("\n ")}"
390
- ].join("\n")
391
- end.join("\n\n")
387
+ [
388
+ "Total threads: #{Thread.list.count}",
389
+ Thread.list.map do |t|
390
+ [
391
+ "#{t.name}: status=#{t.status}; alive=#{t.alive?}",
392
+ t.backtrace.nil? ? 'NO BACKTRACE' : " #{t.backtrace.join("\n ")}"
393
+ ].join("\n")
394
+ end
395
+ ].flatten.join("\n\n")
392
396
  end
393
397
 
394
398
  not_found do
@@ -421,9 +425,20 @@ while #{settings.address} is in '#{settings.network}'"
421
425
  end
422
426
 
423
427
  def score
424
- best = settings.farm.best
428
+ best = Cachy.cache(:a_score, expires_in: 60) { settings.farm.best }
425
429
  raise 'Score is empty, there is something wrong with the Farm!' if best.empty?
426
430
  best[0]
427
431
  end
432
+
433
+ def copy_of(id)
434
+ Tempfile.open([id.to_s, Wallet::EXT]) do |f|
435
+ settings.wallets.find(id) do |wallet|
436
+ IO.write(f, IO.read(wallet.path)) if File.exist?(wallet.path)
437
+ end
438
+ path = f.path
439
+ f.delete if File.size(f.path).zero?
440
+ yield Wallet.new(path)
441
+ end
442
+ end
428
443
  end
429
444
  end