familia 2.0.0.pre2 → 2.0.0.pre4
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 +4 -4
- data/CLAUDE.md +12 -5
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -9
- data/lib/familia/core_ext.rb +2 -2
- data/lib/familia/features/expiration.rb +0 -1
- data/lib/familia/features/relatable_objects.rb +127 -0
- data/lib/familia/features.rb +7 -3
- data/lib/familia/horreum/class_methods.rb +32 -4
- data/lib/familia/secure_identifier.rb +129 -0
- data/lib/familia/utils.rb +7 -96
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -0
- data/try/configuration/scenarios_try.rb +43 -31
- data/try/core/errors_try.rb +10 -10
- data/try/core/extensions_try.rb +56 -23
- data/try/core/familia_extended_try.rb +3 -3
- data/try/core/familia_try.rb +0 -4
- data/try/core/middleware_try.rb +34 -40
- data/try/core/secure_identifier_try.rb +104 -0
- data/try/core/tools_try.rb +52 -36
- data/try/core/utils_try.rb +0 -98
- data/try/datatypes/boolean_try.rb +6 -7
- data/try/datatypes/datatype_base_try.rb +2 -2
- data/try/datatypes/hash_try.rb +0 -1
- data/try/datatypes/list_try.rb +0 -1
- data/try/datatypes/set_try.rb +0 -2
- data/try/datatypes/sorted_set_try.rb +1 -2
- data/try/datatypes/string_try.rb +1 -2
- data/try/edge_cases/empty_identifiers_try.rb +42 -35
- data/try/edge_cases/hash_symbolization_try.rb +5 -5
- data/try/edge_cases/json_serialization_try.rb +12 -13
- data/try/edge_cases/race_conditions_try.rb +46 -49
- data/try/edge_cases/reserved_keywords_try.rb +103 -49
- data/try/edge_cases/string_coercion_try.rb +2 -2
- data/try/edge_cases/ttl_side_effects_try.rb +44 -25
- data/try/features/expiration_try.rb +2 -2
- data/try/features/quantization_try.rb +2 -2
- data/try/features/relatable_objects_try.rb +221 -0
- data/try/features/safe_dump_advanced_try.rb +13 -14
- data/try/features/safe_dump_try.rb +8 -8
- data/try/helpers/test_helpers.rb +10 -12
- data/try/horreum/base_try.rb +9 -9
- data/try/horreum/class_methods_try.rb +27 -30
- data/try/horreum/commands_try.rb +69 -33
- data/try/horreum/initialization_try.rb +4 -4
- data/try/horreum/relations_try.rb +13 -14
- data/try/horreum/serialization_try.rb +3 -3
- data/try/horreum/settings_try.rb +25 -31
- data/try/integration/cross_component_try.rb +45 -35
- data/try/models/customer_safe_dump_try.rb +4 -4
- data/try/models/customer_try.rb +21 -24
- data/try/models/datatype_base_try.rb +0 -1
- data/try/models/familia_object_try.rb +3 -4
- data/try/performance/benchmarks_try.rb +47 -38
- data/try/prototypes/atomic_saves_v4.rb +3 -3
- metadata +15 -12
- data/try/core/refinements_try.rb +0 -39
- /data/try/{pooling/connection_pool_test_try.rb → core/pools_try.rb} +0 -0
- /data/try/{pooling → prototypes/pooling}/README.md +0 -0
- /data/try/{pooling/configurable_stress_test_try.rb → prototypes/pooling/configurable_stress_test.rb} +0 -0
- /data/try/{pooling → prototypes/pooling}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{pooling → prototypes/pooling}/lib/connection_pool_metrics.rb +0 -0
- /data/try/{pooling → prototypes/pooling}/lib/connection_pool_stress_test.rb +0 -0
- /data/try/{pooling → prototypes/pooling}/lib/connection_pool_threading_models.rb +0 -0
- /data/try/{pooling → prototypes/pooling}/lib/visualize_stress_results.rb +0 -0
- /data/try/{pooling/pool_siege_try.rb → prototypes/pooling/pool_siege.rb} +0 -0
- /data/try/{pooling/run_stress_tests_try.rb → prototypes/pooling/run_stress_tests.rb} +0 -0
data/try/core/utils_try.rb
CHANGED
@@ -7,104 +7,6 @@ Familia.debug = false
|
|
7
7
|
|
8
8
|
# Test Familia utility methods and helpers
|
9
9
|
|
10
|
-
##
|
11
|
-
## ID Generation
|
12
|
-
##
|
13
|
-
|
14
|
-
## Familia.generate_id
|
15
|
-
Familia.respond_to?(:generate_id)
|
16
|
-
#=> true
|
17
|
-
|
18
|
-
## Can generate a default base-36 ID
|
19
|
-
id = Familia.generate_id
|
20
|
-
[id.class, id.length > 10, id.match?(/^[a-z0-9]+$/)]
|
21
|
-
#=> [String, true, true]
|
22
|
-
|
23
|
-
## Generated IDs are unique
|
24
|
-
[Familia.generate_id == Familia.generate_id]
|
25
|
-
#=> [false]
|
26
|
-
|
27
|
-
## Can generate an ID with a custom base (hex)
|
28
|
-
hex_id = Familia.generate_id(16)
|
29
|
-
[hex_id.class, hex_id.length == 64, hex_id.match?(/^[a-f0-9]+$/)]
|
30
|
-
#=> [String, true, true]
|
31
|
-
|
32
|
-
## Familia.generate_trace_id
|
33
|
-
Familia.respond_to?(:generate_trace_id)
|
34
|
-
#=> true
|
35
|
-
|
36
|
-
## Can generate a default base-36 trace ID
|
37
|
-
trace_id = Familia.generate_trace_id
|
38
|
-
[trace_id.class, trace_id.length > 5, trace_id.length < 20]
|
39
|
-
#=> [String, true, true]
|
40
|
-
|
41
|
-
## Can generate a trace ID with a custom base (hex)
|
42
|
-
hex_trace_id = Familia.generate_trace_id(16)
|
43
|
-
[hex_trace_id.class, hex_trace_id.length == 16]
|
44
|
-
#=> [String, true]
|
45
|
-
|
46
|
-
## Familia.generate_hex_id
|
47
|
-
Familia.respond_to?(:generate_hex_id)
|
48
|
-
#=> true
|
49
|
-
|
50
|
-
## Can generate a 256-bit hex ID
|
51
|
-
hex_id = Familia.generate_hex_id
|
52
|
-
[hex_id.class, hex_id.length == 64, hex_id.match?(/^[a-f0-9]+$/)]
|
53
|
-
#=> [String, true, true]
|
54
|
-
|
55
|
-
## Familia.generate_hex_trace_id
|
56
|
-
Familia.respond_to?(:generate_hex_trace_id)
|
57
|
-
#=> true
|
58
|
-
|
59
|
-
## Can generate a 64-bit hex trace ID
|
60
|
-
hex_trace_id = Familia.generate_hex_trace_id
|
61
|
-
[hex_trace_id.class, hex_trace_id.length == 16, hex_trace_id.match?(/^[a-f0-9]+$/)]
|
62
|
-
#=> [String, true, true]
|
63
|
-
|
64
|
-
|
65
|
-
##
|
66
|
-
## ID Shortening
|
67
|
-
##
|
68
|
-
|
69
|
-
## Familia.shorten_to_external_id
|
70
|
-
Familia.respond_to?(:shorten_to_external_id)
|
71
|
-
#=> true
|
72
|
-
|
73
|
-
## Can shorten hex ID to external ID (128 bits)
|
74
|
-
hex_id = Familia.generate_hex_id
|
75
|
-
external_id = Familia.shorten_to_external_id(hex_id)
|
76
|
-
[external_id.class, external_id.length < hex_id.length]
|
77
|
-
#=> [String, true]
|
78
|
-
|
79
|
-
## Can shorten hex ID to external ID with custom base (hex)
|
80
|
-
hex_id = Familia.generate_hex_id
|
81
|
-
hex_external_id = Familia.shorten_to_external_id(hex_id, base: 16)
|
82
|
-
[hex_external_id.class, hex_external_id.length == 32]
|
83
|
-
#=> [String, true]
|
84
|
-
|
85
|
-
## Familia.shorten_to_trace_id
|
86
|
-
Familia.respond_to?(:shorten_to_trace_id)
|
87
|
-
#=> true
|
88
|
-
|
89
|
-
## Can shorten hex ID to trace ID (64 bits)
|
90
|
-
hex_id = Familia.generate_hex_id
|
91
|
-
trace_id = Familia.shorten_to_trace_id(hex_id)
|
92
|
-
[trace_id.class, trace_id.length < hex_id.length]
|
93
|
-
#=> [String, true]
|
94
|
-
|
95
|
-
## Can shorten hex ID to trace ID with custom base (hex)
|
96
|
-
hex_id = Familia.generate_hex_id
|
97
|
-
hex_trace_id = Familia.shorten_to_trace_id(hex_id, base: 16)
|
98
|
-
[hex_trace_id.class, hex_trace_id.length == 16]
|
99
|
-
#=> [String, true]
|
100
|
-
|
101
|
-
## Shortened IDs are deterministic
|
102
|
-
hex_id = Familia.generate_hex_id
|
103
|
-
id1 = Familia.shorten_to_external_id(hex_id)
|
104
|
-
id2 = Familia.shorten_to_external_id(hex_id)
|
105
|
-
id1 == id2
|
106
|
-
#=> true
|
107
|
-
|
108
10
|
##
|
109
11
|
## Key helpers
|
110
12
|
##
|
@@ -7,37 +7,36 @@ Familia.debug = false
|
|
7
7
|
|
8
8
|
@hashkey = Familia::HashKey.new 'key'
|
9
9
|
|
10
|
-
|
11
10
|
## Boolean values are returned as strings, on assignment as string
|
12
|
-
@hashkey[
|
11
|
+
@hashkey['test'] = 'true'
|
13
12
|
#=> "true"
|
14
13
|
|
15
14
|
## Boolean values are returned as strings
|
16
|
-
@hashkey[
|
15
|
+
@hashkey['test']
|
17
16
|
#=> "true"
|
18
17
|
|
19
18
|
## Trying to store a boolean value to a hash key raises an exception
|
20
19
|
begin
|
21
|
-
@hashkey[
|
20
|
+
@hashkey['test'] = true
|
22
21
|
rescue Familia::HighRiskFactor => e
|
23
22
|
e.message
|
24
23
|
end
|
25
24
|
#=> "High risk factor for serialization bugs: true<TrueClass>"
|
26
25
|
|
27
26
|
## Boolean values are returned as strings
|
28
|
-
@hashkey[
|
27
|
+
@hashkey['test']
|
29
28
|
#=> "true"
|
30
29
|
|
31
30
|
## Trying to store a nil value to a hash key raises an exception
|
32
31
|
begin
|
33
|
-
@hashkey[
|
32
|
+
@hashkey['test'] = nil
|
34
33
|
rescue Familia::HighRiskFactor => e
|
35
34
|
e.message
|
36
35
|
end
|
37
36
|
#=> "High risk factor for serialization bugs: <NilClass>"
|
38
37
|
|
39
38
|
## The exceptions prevented the hash from being updated
|
40
|
-
@hashkey[
|
39
|
+
@hashkey['test']
|
41
40
|
#=> "true"
|
42
41
|
|
43
42
|
## Clear the hash key
|
@@ -22,7 +22,7 @@ p [@a.name, @b.name]
|
|
22
22
|
#=> true
|
23
23
|
|
24
24
|
## Limiter#qstamp
|
25
|
-
@limiter1.counter.qstamp(10.minutes, '%H:%M',
|
25
|
+
@limiter1.counter.qstamp(10.minutes, '%H:%M', 1_302_468_980)
|
26
26
|
##=> '20:50'
|
27
27
|
|
28
28
|
## Database Types can be stored to quantized stamp suffix
|
@@ -33,7 +33,7 @@ p [@a.name, @b.name]
|
|
33
33
|
@limiter2 = Limiter.new :requests
|
34
34
|
p [@limiter1.default_expiration, @limiter2.default_expiration]
|
35
35
|
p [@limiter1.counter.parent.default_expiration, @limiter2.counter.parent.default_expiration]
|
36
|
-
@limiter2.counter.qstamp(10.minutes, pattern: nil, time:
|
36
|
+
@limiter2.counter.qstamp(10.minutes, pattern: nil, time: 1_302_468_980)
|
37
37
|
#=> 1302468600
|
38
38
|
|
39
39
|
## Database Types can be stored to quantized numeric suffix. This
|
data/try/datatypes/hash_try.rb
CHANGED
data/try/datatypes/list_try.rb
CHANGED
data/try/datatypes/set_try.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require_relative '../../lib/familia'
|
4
4
|
require_relative '../helpers/test_helpers'
|
5
5
|
|
6
|
-
|
7
6
|
@a = Bone.new 'atoken'
|
8
7
|
|
9
8
|
## Familia::SortedSet#add
|
@@ -36,7 +35,7 @@ require_relative '../helpers/test_helpers'
|
|
36
35
|
#=> ['metric1', 'metric2', 'metric3']
|
37
36
|
|
38
37
|
## Familia::SortedSet#rangebyscore with a limit
|
39
|
-
@a.metrics.rangebyscore 1, 3, :
|
38
|
+
@a.metrics.rangebyscore 1, 3, limit: [0, 2]
|
40
39
|
#=> ['metric1', 'metric2']
|
41
40
|
|
42
41
|
## Familia::SortedSet#increment
|
data/try/datatypes/string_try.rb
CHANGED
@@ -14,7 +14,7 @@ require_relative '../helpers/test_helpers'
|
|
14
14
|
#=> 'GREAT!'
|
15
15
|
|
16
16
|
## Familia::String#value=
|
17
|
-
@a.value.value =
|
17
|
+
@a.value.value = 'DECENT!'
|
18
18
|
#=> 'DECENT!'
|
19
19
|
|
20
20
|
## Familia::String#to_s
|
@@ -62,5 +62,4 @@ require_relative '../helpers/test_helpers'
|
|
62
62
|
@ret.value
|
63
63
|
#=> '1050bytes'
|
64
64
|
|
65
|
-
|
66
65
|
@ret.delete!
|
@@ -1,48 +1,55 @@
|
|
1
|
-
|
1
|
+
# try/edge_cases/empty_identifiers_try.rb
|
2
2
|
|
3
3
|
# Test empty identifier edge cases
|
4
|
-
group "Empty Identifier Edge Cases"
|
5
4
|
|
6
|
-
|
7
|
-
@user_class = Class.new(Familia::Horreum) do
|
8
|
-
identifier :email
|
9
|
-
field :name
|
10
|
-
end
|
11
|
-
end
|
5
|
+
require_relative '../helpers/test_helpers'
|
12
6
|
|
13
|
-
try "empty string identifier causes stack overflow" do
|
14
|
-
user = @user_class.new(email: "", name: "Test")
|
15
7
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
false # Unexpected error
|
8
|
+
## empty string identifier handling
|
9
|
+
begin
|
10
|
+
user_class = Class.new(Familia::Horreum) do
|
11
|
+
identifier_field :email
|
12
|
+
field :email
|
13
|
+
field :name
|
23
14
|
end
|
15
|
+
user = user_class.new(email: '', name: 'Test')
|
16
|
+
user.exists? # Test actual behavior with empty identifier
|
17
|
+
rescue SystemStackError
|
18
|
+
'stack_overflow' # Stack overflow occurred
|
19
|
+
rescue StandardError => e
|
20
|
+
e.class.name # Other error occurred
|
24
21
|
end
|
22
|
+
#=> "Familia::NoIdentifier"
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
rescue SystemStackError, Familia::NoIdentifier
|
33
|
-
true # Expected error
|
24
|
+
## nil identifier handling
|
25
|
+
begin
|
26
|
+
user_class = Class.new(Familia::Horreum) do
|
27
|
+
identifier_field :email
|
28
|
+
field :email
|
29
|
+
field :name
|
34
30
|
end
|
31
|
+
user = user_class.new(email: nil, name: 'Test')
|
32
|
+
user.exists?
|
33
|
+
rescue SystemStackError
|
34
|
+
'stack_overflow'
|
35
|
+
rescue Familia::NoIdentifier => e
|
36
|
+
'no_identifier'
|
37
|
+
rescue StandardError => e
|
38
|
+
e.class.name
|
35
39
|
end
|
40
|
+
#=> "no_identifier"
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
## empty identifier validation check
|
43
|
+
begin
|
44
|
+
user_class = Class.new(Familia::Horreum) do
|
45
|
+
identifier_field :email
|
46
|
+
field :email
|
47
|
+
field :name
|
43
48
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
user = user_class.new(email: '', name: 'Test')
|
50
|
+
# Check if identifier is empty
|
51
|
+
user.identifier.to_s.empty?
|
52
|
+
rescue StandardError => e
|
53
|
+
e.class.name
|
48
54
|
end
|
55
|
+
#=> true
|
@@ -16,9 +16,9 @@ class SymbolizeTest < Familia::Horreum
|
|
16
16
|
field :config
|
17
17
|
end
|
18
18
|
|
19
|
-
@test_hash = {
|
19
|
+
@test_hash = { 'name' => 'John', 'age' => 30, 'nested' => { 'theme' => 'dark' } }
|
20
20
|
@test_obj = SymbolizeTest.new
|
21
|
-
@test_obj.id =
|
21
|
+
@test_obj.id = 'symbolize_test_1'
|
22
22
|
@test_obj.config = @test_hash
|
23
23
|
@test_obj.save
|
24
24
|
|
@@ -55,18 +55,18 @@ end
|
|
55
55
|
#=> ["name", "age", "nested"]
|
56
56
|
|
57
57
|
## Nested hash in string result also has string keys
|
58
|
-
@string_result[
|
58
|
+
@string_result['nested'].keys
|
59
59
|
#=> ["theme"]
|
60
60
|
|
61
61
|
## Values are preserved correctly in both cases
|
62
62
|
@symbol_result[:name]
|
63
63
|
#=> "John"
|
64
64
|
|
65
|
-
@string_result[
|
65
|
+
@string_result['name']
|
66
66
|
#=> "John"
|
67
67
|
|
68
68
|
## Arrays are handled correctly too
|
69
|
-
@test_obj.config = [{
|
69
|
+
@test_obj.config = [{ 'item' => 'value' }, 'string', 123]
|
70
70
|
@test_obj.save
|
71
71
|
@array_json = @test_obj.hget('config')
|
72
72
|
#=> "[{\"item\":\"value\"},\"string\",123]"
|
@@ -14,22 +14,21 @@ class JsonTest < Familia::Horreum
|
|
14
14
|
field :simple # This should store simple strings as-is
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
17
|
## Test 1: Store a Hash - should serialize to JSON automatically
|
19
18
|
test_obj = JsonTest.new
|
20
|
-
test_obj.config = { theme:
|
19
|
+
test_obj.config = { theme: 'dark', notifications: true, settings: { volume: 80 } }
|
21
20
|
test_obj.config
|
22
21
|
#=:> Hash
|
23
22
|
|
24
23
|
## Test 2: Store an Array - should serialize to JSON automatically
|
25
24
|
test_obj = JsonTest.new
|
26
|
-
test_obj.tags = [
|
25
|
+
test_obj.tags = %w[ruby valkey json familia]
|
27
26
|
test_obj.tags
|
28
27
|
#=:> Array
|
29
28
|
|
30
29
|
## Test 3: Store a simple string - should remain as string
|
31
30
|
test_obj = JsonTest.new
|
32
|
-
test_obj.simple =
|
31
|
+
test_obj.simple = 'just a string'
|
33
32
|
test_obj.simple
|
34
33
|
#=:> String
|
35
34
|
|
@@ -40,25 +39,25 @@ test_obj.save
|
|
40
39
|
|
41
40
|
## Verify what's actually stored in Database (raw)
|
42
41
|
test_obj = JsonTest.new
|
43
|
-
test_obj.id =
|
44
|
-
test_obj.config = { theme:
|
45
|
-
test_obj.simple =
|
46
|
-
test_obj.tags = [
|
42
|
+
test_obj.id = 'json_test_1'
|
43
|
+
test_obj.config = { theme: 'dark', notifications: true, settings: { volume: 80 } }
|
44
|
+
test_obj.simple = 'just a string'
|
45
|
+
test_obj.tags = %w[ruby valkey json familia]
|
47
46
|
test_obj.save
|
48
47
|
test_obj.hgetall
|
49
48
|
#=> {"id"=>"json_test_1", "config"=>"{\"theme\":\"dark\",\"notifications\":true,\"settings\":{\"volume\":80}}", "tags"=>"[\"ruby\",\"valkey\",\"json\",\"familia\"]", "simple"=>"just a string"}
|
50
49
|
|
51
50
|
## Test 4: Hash should be deserialized back to Hash
|
52
51
|
test_obj = JsonTest.new 'any_id_will_do'
|
53
|
-
puts
|
52
|
+
puts 'Config after refresh:'
|
54
53
|
puts test_obj.config
|
55
|
-
puts
|
54
|
+
puts 'Config class: '
|
56
55
|
[test_obj.config.class, test_obj.config]
|
57
56
|
##=> [Hash, {:theme=>"dark", :notifications=>true, :settings=>{:volume=>80}}]
|
58
57
|
|
59
58
|
## Test 5: Array should be deserialized back to Array
|
60
59
|
test_obj = JsonTest.new 'any_id_will_do'
|
61
|
-
puts
|
60
|
+
puts 'Tags after refresh:'
|
62
61
|
puts test_obj.tags.inspect
|
63
62
|
puts "Tags class: #{test_obj.tags.class}"
|
64
63
|
test_obj.tags.inspect
|
@@ -67,7 +66,7 @@ test_obj.tags
|
|
67
66
|
|
68
67
|
## Test 6: Simple string should remain a string (this works correctly)
|
69
68
|
test_obj = JsonTest.new 'any_id_will_do'
|
70
|
-
puts
|
69
|
+
puts 'Simple after refresh:'
|
71
70
|
puts test_obj.simple.inspect
|
72
71
|
puts "Simple class: #{test_obj.simple.class}"
|
73
72
|
[test_obj.simple.class, test_obj.simple]
|
@@ -77,7 +76,7 @@ puts "Simple class: #{test_obj.simple.class}"
|
|
77
76
|
test_obj = JsonTest.new 'any_id_will_do'
|
78
77
|
puts "\n=== ASYMMETRY DEMONSTRATION ==="
|
79
78
|
puts "Before save: config is #{test_obj.config.class}"
|
80
|
-
test_obj.config = { example:
|
79
|
+
test_obj.config = { example: 'data' }
|
81
80
|
puts "After assignment: config is #{test_obj.config.class}"
|
82
81
|
test_obj.save
|
83
82
|
puts "After save: config is still #{test_obj.config.class}"
|
@@ -1,60 +1,57 @@
|
|
1
|
-
require_relative '../helpers/test_helpers'
|
2
|
-
|
3
1
|
# Test connection race conditions
|
4
|
-
group "Race Conditions Edge Cases"
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
require_relative '../helpers/test_helpers'
|
4
|
+
|
5
|
+
## concurrent connection access test
|
6
|
+
user_class = Class.new(Familia::Horreum) do
|
7
|
+
identifier_field :email
|
8
|
+
field :email
|
9
|
+
field :counter
|
11
10
|
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
user.save
|
16
|
-
|
17
|
-
threads = []
|
18
|
-
results = []
|
19
|
-
|
20
|
-
# Simulate high concurrency
|
21
|
-
10.times do
|
22
|
-
threads << Thread.new do
|
23
|
-
begin
|
24
|
-
user.incr(:counter)
|
25
|
-
results << "success"
|
26
|
-
rescue => e
|
27
|
-
results << "error: #{e.class.name}"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
12
|
+
user = user_class.new(email: 'test@example.com', counter: 0)
|
13
|
+
user.save
|
31
14
|
|
32
|
-
|
15
|
+
threads = []
|
16
|
+
results = []
|
33
17
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
18
|
+
# Simulate high concurrency
|
19
|
+
10.times do
|
20
|
+
threads << Thread.new do
|
21
|
+
user.incr(:counter)
|
22
|
+
results << 'success'
|
23
|
+
rescue StandardError => e
|
24
|
+
results << "error: #{e.class.name}"
|
25
|
+
end
|
39
26
|
end
|
40
27
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
28
|
+
threads.each(&:join)
|
29
|
+
user.delete!
|
30
|
+
|
31
|
+
# Count successful operations
|
32
|
+
successes = results.count { |r| r == 'success' }
|
33
|
+
successes > 0 # Should have some successes
|
34
|
+
#=!> StandardError
|
35
|
+
|
36
|
+
## connection pool stress test
|
37
|
+
## We're just checking whether it completes within a reasonable time frame.
|
38
|
+
## If it does fail either bc of the duration or contention then it's a problem.
|
39
|
+
success_count = 0
|
40
|
+
threads = []
|
41
|
+
mutex = Mutex.new
|
42
|
+
|
43
|
+
# Test concurrent connections
|
44
|
+
100.times do |i|
|
45
|
+
threads << Thread.new do
|
46
|
+
# Try to get a connection and perform an operation
|
47
|
+
dbclient = Familia.dbclient
|
48
|
+
dbclient.ping
|
49
|
+
success_count += 1
|
52
50
|
end
|
51
|
+
end
|
53
52
|
|
54
|
-
|
53
|
+
threads.each(&:join)
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
users.each(&:delete!) rescue nil
|
60
|
-
end
|
55
|
+
# Should have some successful connections
|
56
|
+
success_count > 0
|
57
|
+
#=%> 220
|