knjrbfw 0.0.29 → 0.0.30
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.
- data/VERSION +1 -1
- data/knjrbfw.gemspec +2 -1
- data/lib/knj/datet.rb +204 -6
- data/lib/knj/errors.rb +9 -0
- data/lib/knj/hash_methods.rb +10 -0
- data/lib/knj/http2.rb +62 -1
- data/lib/knj/knjdb/libknjdb.rb +99 -1
- data/lib/knj/knjdb/revision.rb +59 -34
- data/lib/knj/memory_analyzer.rb +13 -0
- data/lib/knj/os.rb +12 -0
- data/lib/knj/process.rb +5 -25
- data/lib/knj/process_meta.rb +13 -0
- data/lib/knj/threadsafe.rb +112 -25
- data/spec/threadsafe_spec.rb +57 -0
- metadata +15 -14
data/lib/knj/knjdb/revision.rb
CHANGED
@@ -1,43 +1,32 @@
|
|
1
|
+
#This class takes a database-schema from a hash and runs it against the database. It then checks that the database matches the given schema.
|
2
|
+
#
|
3
|
+
#===Examples
|
4
|
+
# db = Knj::Db.new(:type => "sqlite3", :path => "test_db.sqlite3")
|
5
|
+
# schema = {
|
6
|
+
# "tables" => {
|
7
|
+
# "columns" => [
|
8
|
+
# {"name" => "id", "type" => "int", "autoincr" => true, "primarykey" => true},
|
9
|
+
# {"name" => "name", "type" => "varchar"},
|
10
|
+
# {"name" => "lastname", "type" => "varchar"}
|
11
|
+
# ],
|
12
|
+
# "indexes" => [
|
13
|
+
# "name",
|
14
|
+
# {"name" => "lastname", "columns" => ["lastname"]}
|
15
|
+
# ]
|
16
|
+
# }
|
17
|
+
# }
|
18
|
+
#
|
19
|
+
# rev = Knj::Db::Revision.new
|
20
|
+
# rev.init_db("db" => db, "schema" => schema)
|
1
21
|
class Knj::Db::Revision
|
2
22
|
def initialize(args = {})
|
3
23
|
@args = args
|
4
24
|
end
|
5
25
|
|
6
|
-
#This method checks if certain rows are present in a table based on a hash.
|
7
|
-
def rows_init(args)
|
8
|
-
db = args["db"]
|
9
|
-
table = args["table"]
|
10
|
-
|
11
|
-
raise "No db given." if !db
|
12
|
-
raise "No table given." if !table
|
13
|
-
|
14
|
-
args["rows"].each do |row_data|
|
15
|
-
if row_data["find_by"]
|
16
|
-
find_by = row_data["find_by"]
|
17
|
-
elsif row_data["data"]
|
18
|
-
find_by = row_data["data"]
|
19
|
-
else
|
20
|
-
raise "Could not figure out the find-by."
|
21
|
-
end
|
22
|
-
|
23
|
-
rows_found = 0
|
24
|
-
args["db"].select(table.name, find_by) do |d_rows|
|
25
|
-
rows_found += 1
|
26
|
-
|
27
|
-
if Knj::ArrayExt.hash_diff?(Knj::ArrayExt.hash_sym(row_data["data"]), Knj::ArrayExt.hash_sym(d_rows), {"h2_to_h1" => false})
|
28
|
-
print "Data was not right - updating row: #{JSON.generate(row_data["data"])}\n" if args["debug"]
|
29
|
-
args["db"].update(table.name, row_data["data"], d_rows)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
if rows_found == 0
|
34
|
-
print "Inserting row: #{JSON.generate(row_data["data"])}\n" if args["debug"]
|
35
|
-
table.insert(row_data["data"])
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
26
|
#This initializes a database-structure and content based on a schema-hash.
|
27
|
+
#===Examples
|
28
|
+
# dbrev = Knj::Db::Revision.new
|
29
|
+
# dbrev.init_db("db" => db_obj, "schema" => schema_hash)
|
41
30
|
def init_db(args)
|
42
31
|
schema = args["schema"]
|
43
32
|
db = args["db"]
|
@@ -283,4 +272,40 @@ class Knj::Db::Revision
|
|
283
272
|
tables.clear
|
284
273
|
tables = nil
|
285
274
|
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
#This method checks if certain rows are present in a table based on a hash.
|
279
|
+
def rows_init(args)
|
280
|
+
db = args["db"]
|
281
|
+
table = args["table"]
|
282
|
+
|
283
|
+
raise "No db given." if !db
|
284
|
+
raise "No table given." if !table
|
285
|
+
|
286
|
+
args["rows"].each do |row_data|
|
287
|
+
if row_data["find_by"]
|
288
|
+
find_by = row_data["find_by"]
|
289
|
+
elsif row_data["data"]
|
290
|
+
find_by = row_data["data"]
|
291
|
+
else
|
292
|
+
raise "Could not figure out the find-by."
|
293
|
+
end
|
294
|
+
|
295
|
+
rows_found = 0
|
296
|
+
args["db"].select(table.name, find_by) do |d_rows|
|
297
|
+
rows_found += 1
|
298
|
+
|
299
|
+
if Knj::ArrayExt.hash_diff?(Knj::ArrayExt.hash_sym(row_data["data"]), Knj::ArrayExt.hash_sym(d_rows), {"h2_to_h1" => false})
|
300
|
+
print "Data was not right - updating row: #{JSON.generate(row_data["data"])}\n" if args["debug"]
|
301
|
+
args["db"].update(table.name, row_data["data"], d_rows)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
if rows_found == 0
|
306
|
+
print "Inserting row: #{JSON.generate(row_data["data"])}\n" if args["debug"]
|
307
|
+
table.insert(row_data["data"])
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
286
311
|
end
|
data/lib/knj/memory_analyzer.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
#This class contains methods to debug memory-leaks. It prints its collected information as HTML.
|
2
|
+
#===Examples
|
3
|
+
# ma = Knj::Memory_analyzer.new
|
4
|
+
# ma.write
|
1
5
|
class Knj::Memory_analyzer
|
6
|
+
#Initialized various objects.
|
2
7
|
def initialize
|
3
8
|
@printed = {}
|
4
9
|
end
|
5
10
|
|
11
|
+
#Writes all available memory-analyzer-information to the given IO.
|
6
12
|
def write(to = $stdout)
|
7
13
|
to.print "<div style=\"width: 600px;\">\n"
|
8
14
|
|
@@ -24,6 +30,7 @@ class Knj::Memory_analyzer
|
|
24
30
|
to.print "</div>\n"
|
25
31
|
end
|
26
32
|
|
33
|
+
#Writes information about the garbage-collector to the given IO as HTML.
|
27
34
|
def garbage_collector(to = $stdout)
|
28
35
|
to.print "<h1>Garbage collector</h1>\n"
|
29
36
|
|
@@ -36,6 +43,7 @@ class Knj::Memory_analyzer
|
|
36
43
|
GC.start
|
37
44
|
end
|
38
45
|
|
46
|
+
#Collects and writes out a lot of information about the spawned hashes as HTML to the given IO.
|
39
47
|
def hashes(to = $stdout)
|
40
48
|
hashes = {}
|
41
49
|
|
@@ -94,6 +102,7 @@ class Knj::Memory_analyzer
|
|
94
102
|
to.write "</table>\n"
|
95
103
|
end
|
96
104
|
|
105
|
+
#Collects and writes a lot of information about the spawned arrays as HTML to the given IO.
|
97
106
|
def arrays(to = $stdout)
|
98
107
|
arrays = {}
|
99
108
|
|
@@ -152,6 +161,7 @@ class Knj::Memory_analyzer
|
|
152
161
|
to.write "</table>\n"
|
153
162
|
end
|
154
163
|
|
164
|
+
#Collects a lot of information and writes a lot of info about the spawned global variables as HTML to the given IO.
|
155
165
|
def global_vars(to = $stdout)
|
156
166
|
to.print "<h1>Global variables</h1>\n"
|
157
167
|
to.print "<table class=\"global_variables list\">\n"
|
@@ -195,6 +205,7 @@ class Knj::Memory_analyzer
|
|
195
205
|
to.print "</table>\n"
|
196
206
|
end
|
197
207
|
|
208
|
+
#Collects information about the spawned classes and writes it as HTML to the given IO.
|
198
209
|
def constants(to = $stdout)
|
199
210
|
to.print "<h1>Constants</h1>\n"
|
200
211
|
to.print "<table class=\"memory_analyzer list\">\n"
|
@@ -219,6 +230,7 @@ class Knj::Memory_analyzer
|
|
219
230
|
to.print "</table>\n"
|
220
231
|
end
|
221
232
|
|
233
|
+
#Writes information about the given mod and submod to the given IO as HTML.
|
222
234
|
def write_constant(to, mod, submod)
|
223
235
|
submod_s = submod.to_s
|
224
236
|
|
@@ -278,6 +290,7 @@ class Knj::Memory_analyzer
|
|
278
290
|
end
|
279
291
|
end
|
280
292
|
|
293
|
+
#This class is used to calculate a guessed amount of memory the given object requires.
|
281
294
|
class Knj::Memory_analyzer::Object_size_counter
|
282
295
|
def initialize(obj)
|
283
296
|
@checked = {}
|
data/lib/knj/os.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module Knj::Os
|
2
|
+
#Returns the path of the home-dir as a string.
|
3
|
+
#===Examples
|
4
|
+
# print "Looks like the current user uses Mozilla software?" if File.exists?("#{Knj::Os.homedir}/.mozilla")
|
2
5
|
def self.homedir
|
3
6
|
if ENV["USERPROFILE"]
|
4
7
|
homedir = ENV["USERPROFILE"]
|
@@ -14,6 +17,8 @@ module Knj::Os
|
|
14
17
|
end
|
15
18
|
|
16
19
|
#This method was created to make up for the fact that Dir.tmpdir sometimes returns empty strings??
|
20
|
+
#===Examples
|
21
|
+
# tmp_db_path = "#{Knj::Os.tmpdir}/temp_db.sqlite3"
|
17
22
|
def self.tmpdir
|
18
23
|
require "tmpdir"
|
19
24
|
tmpdir = Dir.tmpdir.to_s.strip
|
@@ -26,6 +31,9 @@ module Knj::Os
|
|
26
31
|
raise "Could not figure out temp-dir."
|
27
32
|
end
|
28
33
|
|
34
|
+
#This method returns the username of the current user.
|
35
|
+
#===Examples
|
36
|
+
# print "I can do what I want, I am root!" if Knj::Os.whoami == "root"
|
29
37
|
def self.whoami
|
30
38
|
if ENV["USERNAME"]
|
31
39
|
whoami = ENV["USERNAME"]
|
@@ -40,6 +48,10 @@ module Knj::Os
|
|
40
48
|
return whoami
|
41
49
|
end
|
42
50
|
|
51
|
+
#Returns the operating system a string.
|
52
|
+
#===Examples
|
53
|
+
# print "Can I please move to another machine?" if Knj::Os.os == "windows"
|
54
|
+
# print "I like it better now." if Knj::Os.os == "linux"
|
43
55
|
def self.os
|
44
56
|
if ENV["OS"]
|
45
57
|
teststring = ENV["OS"].to_s
|
data/lib/knj/process.rb
CHANGED
@@ -182,21 +182,15 @@ class Knj::Process
|
|
182
182
|
self.answer(id, "ok")
|
183
183
|
when "send_block_buffer"
|
184
184
|
Knj::Thread.new do
|
185
|
-
mutex = Mutex.new #Protecting 'buffer_answers'-variable. Crashing JRuby...
|
186
185
|
result_obj = Knj::Process::Resultobject.new(:process => self, :id => id, :obj => obj)
|
187
186
|
block_res = nil
|
188
187
|
buffer_done = false
|
189
188
|
|
190
189
|
begin
|
191
|
-
buffer_answers =
|
192
|
-
|
190
|
+
buffer_answers = Knj::Threadsafe.std_array #JRuby needs the threadsafety.
|
193
191
|
buffer_thread = Knj::Thread.new do
|
194
|
-
dobreak = false
|
195
192
|
loop do
|
196
|
-
arr =
|
197
|
-
mutex.synchronize do
|
198
|
-
arr = buffer_answers.shift(200)
|
199
|
-
end
|
193
|
+
arr = buffer_answers.shift(200)
|
200
194
|
|
201
195
|
if !arr.empty?
|
202
196
|
$stderr.print "Sending: #{arr.length} results.\n" if @debug
|
@@ -206,14 +200,7 @@ class Knj::Process
|
|
206
200
|
sleep 0.05
|
207
201
|
end
|
208
202
|
|
209
|
-
if buffer_done
|
210
|
-
mutex.synchronize do
|
211
|
-
$stderr.print "Buffer-answers: #{buffer_answers.length}, #{buffer_answers.empty?}\n" if @debug
|
212
|
-
dobreak = true if buffer_answers.empty? #since a break will not affect the real loop in Mutex#synchronize set this variable.
|
213
|
-
end
|
214
|
-
|
215
|
-
break if dobreak
|
216
|
-
end
|
203
|
+
break if buffer_done and buffer_answers.empty?
|
217
204
|
end
|
218
205
|
end
|
219
206
|
|
@@ -222,12 +209,7 @@ class Knj::Process
|
|
222
209
|
count = 0
|
223
210
|
block_res = @on_rec.call(result_obj) do |answer_block|
|
224
211
|
loop do
|
225
|
-
|
226
|
-
mutex.synchronize do
|
227
|
-
len = buffer_answers.length
|
228
|
-
end
|
229
|
-
|
230
|
-
if len > 1000
|
212
|
+
if buffer_answers.length > 1000
|
231
213
|
$stderr.print "Buffer is more than 1000 - sleeping and tries again in 0.05 sec.\n" if @debug
|
232
214
|
sleep 0.05
|
233
215
|
else
|
@@ -236,9 +218,7 @@ class Knj::Process
|
|
236
218
|
end
|
237
219
|
|
238
220
|
count += 1
|
239
|
-
|
240
|
-
buffer_answers << answer_block
|
241
|
-
end
|
221
|
+
buffer_answers << answer_block
|
242
222
|
|
243
223
|
if count >= 100
|
244
224
|
count = 0
|
data/lib/knj/process_meta.rb
CHANGED
@@ -1,9 +1,22 @@
|
|
1
1
|
require "#{$knjpath}process"
|
2
2
|
require "#{$knjpath}os"
|
3
3
|
|
4
|
+
#This class can spawn another Ruby-process and manipulate it to create objects, evaluate code, create proxy-objects and other stuff in that process.
|
5
|
+
#===Examples
|
6
|
+
# This will create another Ruby-process, spawn an integer with the value of 5, run upto(10) and return each block to the current block. In the end the subprocess is terminated.
|
7
|
+
# Knj::Process_meta.new do |subproc|
|
8
|
+
# proxy_int = subproc.new(:Integer, 5)
|
9
|
+
# proxy_int.upto(10) do |i|
|
10
|
+
# print "Number: #{i}\n"
|
11
|
+
# end
|
12
|
+
# end
|
4
13
|
class Knj::Process_meta
|
5
14
|
attr_reader :process, :pid
|
6
15
|
|
16
|
+
#===Examples
|
17
|
+
#Knj::Process_meta.new("id" => "my_subproc") #Will make this ID be shown in the command, so you can recocknize it from "ps aux".
|
18
|
+
#Knj::Process_meta.new("exec_path" => "ruby1.9.1") #If you want a certain Ruby-command to be used when starting the subprocess, instead of detecting the current one.
|
19
|
+
#Knj::Process_meta.new("debug" => true, "debug_err" => true) #Enables various debug-messages to be printed.
|
7
20
|
def initialize(args = {})
|
8
21
|
@args = args
|
9
22
|
@objects = {}
|
data/lib/knj/threadsafe.rb
CHANGED
@@ -1,33 +1,120 @@
|
|
1
|
-
|
2
|
-
#Minor modifications from Headius's original lib like submoduled, lower-case-safe and more...
|
1
|
+
require "monitor"
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
#This module contains various tools to handle thread-safety easily and pretty.
|
4
|
+
module Knj::Threadsafe
|
5
|
+
#JRuby can corrupt an array in a threadded env. Use this method to only get a synchronized array when running JRuby and not having to write "if RUBY_ENGINE"-stuff.
|
6
|
+
def self.std_array
|
7
|
+
return Synced_array.new if RUBY_ENGINE == "jruby"
|
8
|
+
return []
|
9
|
+
end
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
#Instances of this class proxies calls to a given-object by using a mutex or monitor.
|
12
|
+
#
|
13
|
+
#==== Examples
|
14
|
+
# threadsafe_array = Knj::Threadsafe::Proxy.new(:obj => [])
|
15
|
+
# threadsafe_array << 5
|
16
|
+
# ret = threadsafe_array[0]
|
17
|
+
#
|
18
|
+
# threadsafe_array = Knj::Threadsafe::Proxy.new(:obj => [], :monitor => true)
|
19
|
+
class Proxy
|
20
|
+
#Spawn needed vars.
|
21
|
+
def initialize(args)
|
22
|
+
if args[:monitor]
|
23
|
+
@mutex = Monitor.new
|
24
|
+
elsif args[:mutex]
|
25
|
+
@mutex = args[:mutex]
|
26
|
+
else
|
27
|
+
@mutex = Mutex.new
|
28
|
+
end
|
29
|
+
|
30
|
+
@obj = args[:obj]
|
15
31
|
end
|
16
32
|
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
33
|
+
#Proxies all calls to this object through the mutex.
|
34
|
+
def method_missing(method_name, *args, &block)
|
35
|
+
@mutex.synchronize do
|
36
|
+
@obj.__send__(method_name, *args, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#This module can be included on a class to make all method-calls synchronized (by using monitor). Examples with array and hash are below.
|
42
|
+
#
|
43
|
+
#===Examples
|
44
|
+
# class MySyncedClass < SomeOtherClassThatNeedsToBeSynchronized
|
45
|
+
# include Knj::Threadsafe::Monitored
|
46
|
+
# end
|
47
|
+
module Monitored
|
48
|
+
def self.included(base)
|
49
|
+
Knj::Strings.const_get_full(base.to_s).class_eval do
|
50
|
+
self.instance_methods.each do |method_name|
|
51
|
+
#These two methods create warnings under JRuby.
|
52
|
+
if RUBY_ENGINE == "jruby"
|
53
|
+
next if method_name == :instance_exec or method_name == :instance_eval
|
54
|
+
end
|
55
|
+
|
56
|
+
new_method_name = "_ts_#{method_name}"
|
57
|
+
alias_method(new_method_name, method_name)
|
58
|
+
|
59
|
+
define_method method_name do |*args, &block|
|
60
|
+
#Need to use monitor, since the internal calls might have to run not-synchronized, and we have just overwritten the internal methods.
|
61
|
+
@_ts_mutex = Monitor.new if !@_ts_mutex
|
62
|
+
@_ts_mutex.synchronize do
|
63
|
+
return self._ts___send__(new_method_name, *args, &block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#This module can be included on a class to make all method-calls synchronized (by using mutex). Examples with array and hash are below.
|
72
|
+
#
|
73
|
+
#===Examples
|
74
|
+
# class MySyncedClass < SomeOtherClassThatNeedsToBeSynchronized
|
75
|
+
# include Knj::Threadsafe::Mutexed
|
76
|
+
# end
|
77
|
+
module Mutexed
|
78
|
+
def self.included(base)
|
79
|
+
Knj::Strings.const_get_full(base.to_s).class_eval do
|
80
|
+
self.instance_methods.each do |method_name|
|
81
|
+
#These two methods create warnings under JRuby.
|
82
|
+
if RUBY_ENGINE == "jruby"
|
83
|
+
next if method_name == :instance_exec or method_name == :instance_eval
|
84
|
+
end
|
85
|
+
|
86
|
+
new_method_name = "_ts_#{method_name}"
|
87
|
+
alias_method(new_method_name, method_name)
|
88
|
+
|
89
|
+
define_method method_name do |*args, &block|
|
90
|
+
#Need to use monitor, since the internal calls might have to run not-synchronized, and we have just overwritten the internal methods.
|
91
|
+
@_ts_mutex = Mutex.new if !@_ts_mutex
|
92
|
+
@_ts_mutex.synchronize do
|
93
|
+
return self._ts___send__(new_method_name, *args, &block)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
24
98
|
end
|
25
99
|
end
|
26
|
-
|
27
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
100
|
+
|
101
|
+
#Predefined synchronized array.
|
102
|
+
#
|
103
|
+
#===Examples
|
104
|
+
# arr = Knj::Threadsafe::Synced_array.new
|
105
|
+
# arr << 5
|
106
|
+
# ret = arr[0]
|
107
|
+
class Synced_array < ::Array
|
108
|
+
include Mutexed
|
109
|
+
end
|
110
|
+
|
111
|
+
#Predefined synchronized hash.
|
112
|
+
#
|
113
|
+
#===Examples
|
114
|
+
# h = Knj::Threadsafe::Synced_hash.new
|
115
|
+
# h['test'] = 'trala'
|
116
|
+
# ret = h['test']
|
117
|
+
class Synced_hash < ::Hash
|
118
|
+
include Mutexed
|
32
119
|
end
|
33
120
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Threadsafe" do
|
4
|
+
it "should be able to spawn threadsafe proxy-objects" do
|
5
|
+
require "knjrbfw"
|
6
|
+
|
7
|
+
arr = Knj::Threadsafe::Proxy.new(:obj => {})
|
8
|
+
|
9
|
+
0.upto(5) do |i|
|
10
|
+
arr[i] = i
|
11
|
+
end
|
12
|
+
|
13
|
+
Knj::Thread.new do
|
14
|
+
arr.each do |key, val|
|
15
|
+
res = key + val
|
16
|
+
sleep 0.1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
5.upto(10) do |i|
|
21
|
+
arr[i] = i
|
22
|
+
sleep 0.1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be able to spawn special classes" do
|
27
|
+
require "knjrbfw"
|
28
|
+
|
29
|
+
#Create new synchronized hash.
|
30
|
+
arr = Knj::Threadsafe::Synced_hash.new
|
31
|
+
|
32
|
+
#Make sure we get the right results.
|
33
|
+
arr[1] = 2
|
34
|
+
|
35
|
+
res = arr[1]
|
36
|
+
raise "Expected 2 but got '#{res}'." if res != 2
|
37
|
+
|
38
|
+
#Set some values to test with.
|
39
|
+
0.upto(5) do |i|
|
40
|
+
arr[i] = i
|
41
|
+
end
|
42
|
+
|
43
|
+
#Try to call through each through a thread and then also try to set new values, which normally would crash the hash.
|
44
|
+
Knj::Thread.new do
|
45
|
+
arr.each do |key, val|
|
46
|
+
res = key + val
|
47
|
+
sleep 0.1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#This should not crash it, since they should wait for each other.
|
52
|
+
5.upto(10) do |i|
|
53
|
+
arr[i] = i
|
54
|
+
sleep 0.1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|