oj 3.7.4 → 3.13.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1352 -0
  3. data/README.md +29 -8
  4. data/RELEASE_NOTES.md +61 -0
  5. data/ext/oj/buf.h +53 -72
  6. data/ext/oj/cache.c +326 -0
  7. data/ext/oj/cache.h +21 -0
  8. data/ext/oj/cache8.c +61 -64
  9. data/ext/oj/cache8.h +12 -39
  10. data/ext/oj/circarray.c +37 -43
  11. data/ext/oj/circarray.h +16 -17
  12. data/ext/oj/code.c +165 -179
  13. data/ext/oj/code.h +27 -29
  14. data/ext/oj/compat.c +174 -194
  15. data/ext/oj/custom.c +809 -866
  16. data/ext/oj/debug.c +132 -0
  17. data/ext/oj/dump.c +848 -863
  18. data/ext/oj/dump.h +81 -67
  19. data/ext/oj/dump_compat.c +85 -123
  20. data/ext/oj/dump_leaf.c +100 -188
  21. data/ext/oj/dump_object.c +527 -656
  22. data/ext/oj/dump_strict.c +315 -338
  23. data/ext/oj/encode.h +7 -34
  24. data/ext/oj/encoder.c +43 -0
  25. data/ext/oj/err.c +40 -29
  26. data/ext/oj/err.h +48 -48
  27. data/ext/oj/extconf.rb +17 -4
  28. data/ext/oj/fast.c +1070 -1087
  29. data/ext/oj/intern.c +301 -0
  30. data/ext/oj/intern.h +26 -0
  31. data/ext/oj/mimic_json.c +469 -436
  32. data/ext/oj/object.c +525 -593
  33. data/ext/oj/odd.c +154 -138
  34. data/ext/oj/odd.h +37 -38
  35. data/ext/oj/oj.c +1325 -986
  36. data/ext/oj/oj.h +333 -316
  37. data/ext/oj/parse.c +1002 -846
  38. data/ext/oj/parse.h +92 -87
  39. data/ext/oj/parser.c +1557 -0
  40. data/ext/oj/parser.h +91 -0
  41. data/ext/oj/rails.c +888 -878
  42. data/ext/oj/rails.h +11 -14
  43. data/ext/oj/reader.c +141 -147
  44. data/ext/oj/reader.h +73 -89
  45. data/ext/oj/resolve.c +41 -62
  46. data/ext/oj/resolve.h +7 -9
  47. data/ext/oj/rxclass.c +71 -75
  48. data/ext/oj/rxclass.h +18 -19
  49. data/ext/oj/saj.c +443 -486
  50. data/ext/oj/saj2.c +602 -0
  51. data/ext/oj/scp.c +88 -113
  52. data/ext/oj/sparse.c +787 -709
  53. data/ext/oj/stream_writer.c +133 -159
  54. data/ext/oj/strict.c +127 -118
  55. data/ext/oj/string_writer.c +230 -249
  56. data/ext/oj/trace.c +34 -41
  57. data/ext/oj/trace.h +19 -19
  58. data/ext/oj/usual.c +1254 -0
  59. data/ext/oj/util.c +136 -0
  60. data/ext/oj/util.h +20 -0
  61. data/ext/oj/val_stack.c +59 -67
  62. data/ext/oj/val_stack.h +91 -129
  63. data/ext/oj/validate.c +46 -0
  64. data/ext/oj/wab.c +342 -353
  65. data/lib/oj/bag.rb +1 -0
  66. data/lib/oj/easy_hash.rb +5 -4
  67. data/lib/oj/error.rb +1 -1
  68. data/lib/oj/json.rb +1 -1
  69. data/lib/oj/mimic.rb +48 -14
  70. data/lib/oj/saj.rb +20 -6
  71. data/lib/oj/state.rb +8 -7
  72. data/lib/oj/version.rb +2 -2
  73. data/lib/oj.rb +0 -8
  74. data/pages/Compatibility.md +1 -1
  75. data/pages/JsonGem.md +15 -0
  76. data/pages/Modes.md +53 -46
  77. data/pages/Options.md +72 -11
  78. data/pages/Parser.md +309 -0
  79. data/pages/Rails.md +73 -22
  80. data/pages/Security.md +1 -1
  81. data/test/activerecord/result_test.rb +7 -2
  82. data/test/activesupport5/abstract_unit.rb +45 -0
  83. data/test/activesupport5/decoding_test.rb +68 -60
  84. data/test/activesupport5/encoding_test.rb +111 -96
  85. data/test/activesupport5/encoding_test_cases.rb +33 -25
  86. data/test/activesupport5/test_helper.rb +43 -21
  87. data/test/activesupport5/time_zone_test_helpers.rb +18 -3
  88. data/test/activesupport6/abstract_unit.rb +44 -0
  89. data/test/activesupport6/decoding_test.rb +133 -0
  90. data/test/activesupport6/encoding_test.rb +507 -0
  91. data/test/activesupport6/encoding_test_cases.rb +98 -0
  92. data/test/activesupport6/test_common.rb +17 -0
  93. data/test/activesupport6/test_helper.rb +163 -0
  94. data/test/activesupport6/time_zone_test_helpers.rb +39 -0
  95. data/test/activesupport7/abstract_unit.rb +49 -0
  96. data/test/activesupport7/decoding_test.rb +125 -0
  97. data/test/activesupport7/encoding_test.rb +486 -0
  98. data/test/activesupport7/encoding_test_cases.rb +104 -0
  99. data/test/activesupport7/time_zone_test_helpers.rb +47 -0
  100. data/test/bar.rb +6 -12
  101. data/test/baz.rb +16 -0
  102. data/test/bug.rb +16 -0
  103. data/test/foo.rb +69 -75
  104. data/test/helper.rb +16 -0
  105. data/test/json_gem/json_common_interface_test.rb +8 -3
  106. data/test/json_gem/json_generator_test.rb +18 -4
  107. data/test/json_gem/json_parser_test.rb +9 -0
  108. data/test/json_gem/test_helper.rb +12 -0
  109. data/test/mem.rb +33 -0
  110. data/test/perf.rb +1 -1
  111. data/test/perf_dump.rb +50 -0
  112. data/test/perf_once.rb +58 -0
  113. data/test/perf_parser.rb +189 -0
  114. data/test/perf_scp.rb +11 -10
  115. data/test/perf_strict.rb +17 -23
  116. data/test/prec.rb +23 -0
  117. data/test/sample_json.rb +1 -1
  118. data/test/test_compat.rb +46 -10
  119. data/test/test_custom.rb +147 -8
  120. data/test/test_fast.rb +62 -2
  121. data/test/test_file.rb +25 -2
  122. data/test/test_gc.rb +13 -0
  123. data/test/test_generate.rb +21 -0
  124. data/test/test_hash.rb +11 -1
  125. data/test/test_integer_range.rb +7 -2
  126. data/test/test_object.rb +85 -9
  127. data/test/test_parser.rb +27 -0
  128. data/test/test_parser_saj.rb +335 -0
  129. data/test/test_parser_usual.rb +217 -0
  130. data/test/test_rails.rb +35 -0
  131. data/test/test_saj.rb +1 -1
  132. data/test/test_scp.rb +5 -5
  133. data/test/test_strict.rb +26 -1
  134. data/test/test_various.rb +87 -65
  135. data/test/test_wab.rb +2 -0
  136. data/test/test_writer.rb +19 -2
  137. data/test/tests.rb +1 -1
  138. data/test/zoo.rb +13 -0
  139. metadata +60 -110
  140. data/ext/oj/hash.c +0 -163
  141. data/ext/oj/hash.h +0 -46
  142. data/ext/oj/hash_test.c +0 -512
data/README.md CHANGED
@@ -1,11 +1,14 @@
1
1
  # [![{}j](http://www.ohler.com/dev/images/oj_comet_64.svg)](http://www.ohler.com/oj) gem
2
2
 
3
- [![Build Status](https://img.shields.io/travis/ohler55/oj/master.svg)](http://travis-ci.org/ohler55/oj?branch=master) [![AppVeyor](https://img.shields.io/appveyor/ci/ohler55/oj/master.svg)](https://ci.appveyor.com/project/ohler55/oj) ![Gem](https://img.shields.io/gem/v/oj.svg) ![Gem](https://img.shields.io/gem/dt/oj.svg) [![SemVer compatibility](https://api.dependabot.com/badges/compatibility_score?dependency-name=oj&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=oj&package-manager=bundler&version-scheme=semver) [![TideLift](https://tidelift.com/badges/github/ohler55/oj)](https://tidelift.com/subscription/pkg/rubygems-oj?utm_source=rubygems-oj&utm_medium=referral&utm_campaign=readme)
3
+ [![Build Status](https://img.shields.io/github/workflow/status/ohler55/oj/CI?logo=github)](https://github.com/ohler55/oj/actions/workflows/CI.yml)
4
+ ![Gem](https://img.shields.io/gem/v/oj.svg)
5
+ ![Gem](https://img.shields.io/gem/dt/oj.svg)
6
+ [![SemVer compatibility](https://api.dependabot.com/badges/compatibility_score?dependency-name=oj&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=oj&package-manager=bundler&version-scheme=semver)
7
+ [![TideLift](https://tidelift.com/badges/github/ohler55/oj)](https://tidelift.com/subscription/pkg/rubygems-oj?utm_source=rubygems-oj&utm_medium=referral&utm_campaign=readme)
4
8
 
5
9
  A *fast* JSON parser and Object marshaller as a Ruby gem.
6
10
 
7
- Version 3.0 is out! 3.0 provides better json gem and Rails compatibility. It
8
- also provides additional optimization options.
11
+ Version 3.13 is out with a much faster parser (`Oj::Parser`) and option isolation.
9
12
 
10
13
  ## Using
11
14
 
@@ -40,9 +43,18 @@ or in Bundler:
40
43
  gem 'oj'
41
44
  ```
42
45
 
46
+ ## Rails and json quickstart
47
+
48
+ See the Quickstart sections of the [Rails](pages/Rails.md) and [json](pages/JsonGem.md) docs.
49
+
50
+ ## multi_json
51
+
52
+ Code which uses [multi_json](https://github.com/intridea/multi_json)
53
+ will automatically prefer Oj if it is installed.
54
+
43
55
  ## Support
44
56
 
45
- [Get supported Oj with a Tidelift Subscription.](https://tidelift.com/subscription/pkg/rubygems-oj?utm_source=rubygems-oj&utm_medium=referral&utm_campaign=readme)
57
+ [Get supported Oj with a Tidelift Subscription.](https://tidelift.com/subscription/pkg/rubygems-oj?utm_source=rubygems-oj&utm_medium=referral&utm_campaign=readme) Security updates are [supported](https://tidelift.com/security).
46
58
 
47
59
  ## Further Reading
48
60
 
@@ -50,7 +62,7 @@ For more details on options, modes, advanced features, and more follow these
50
62
  links.
51
63
 
52
64
  - [{file:Options.md}](pages/Options.md) for parse and dump options.
53
- - [{file:Modes.md}](pages/Modes.md) for details on modes for strict JSON compliance, mimicing the JSON gem, and mimicing Rails and ActiveSupport behavior.
65
+ - [{file:Modes.md}](pages/Modes.md) for details on modes for strict JSON compliance, mimicking the JSON gem, and mimicking Rails and ActiveSupport behavior.
54
66
  - [{file:JsonGem.md}](pages/JsonGem.md) includes more details on json gem compatibility and use.
55
67
  - [{file:Rails.md}](pages/Rails.md) includes more details on Rails and ActiveSupport compatibility and use.
56
68
  - [{file:Custom.md}](pages/Custom.md) includes more details on Custom mode.
@@ -61,11 +73,11 @@ links.
61
73
 
62
74
  ## Releases
63
75
 
64
- See [{file:CHANGELOG.md}](CHANGELOG.md)
76
+ See [{file:CHANGELOG.md}](CHANGELOG.md) and [{file:RELEASE_NOTES.md}](RELEASE_NOTES.md)
65
77
 
66
78
  ## Links
67
79
 
68
- - *Documentation*: http://www.ohler.com/oj/doc, http://rubydoc.info/gems/oj
80
+ - *Documentation*: http://www.ohler.com/oj/doc, http://rubydoc.info/gems/oj
69
81
 
70
82
  - *GitHub* *repo*: https://github.com/ohler55/oj
71
83
 
@@ -93,4 +105,13 @@ Follow [@peterohler on Twitter](http://twitter.com/peterohler) for announcements
93
105
 
94
106
  - *OjC, a C JSON parser*: https://www.ohler.com/ojc also at https://github.com/ohler55/ojc
95
107
 
96
- - *Piper Push Cache, push JSON to browsers*: http://www.piperpushcache.com
108
+ - *Agoo, a high performance Ruby web server supporting GraphQL on GitHub*: https://github.com/ohler55/agoo
109
+
110
+ - *Agoo-C, a high performance C web server supporting GraphQL on GitHub*: https://github.com/ohler55/agoo-c
111
+
112
+ #### Contributing
113
+
114
+ + Provide a Pull Request off the `develop` branch.
115
+ + Report a bug
116
+ + Suggest an idea
117
+ + Code is now formatted with the clang-format tool with the configuration file in the root of the repo.
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,61 @@
1
+ # RELEASE NOTES
2
+
3
+ The release notes here are organized by release. For a list of changes
4
+ see the See [{file:CHANGELOG.md}](CHANGELOG.md) file. In this file are
5
+ the steps to take to aid in keeping things rolling after updating to
6
+ the latest version.
7
+
8
+ ## 3.13.7
9
+
10
+ The default for JSON when mimicked by Oj is now to set
11
+ `:allow_invalid_unicode`. To change that behavior JSON.load, set that
12
+ option to false.
13
+
14
+ ## 3.13.x
15
+
16
+ This release included a new cache that performs better than the
17
+ earlier cache and a new high performance parser.
18
+
19
+ ### Cache
20
+
21
+ The new cache includes a least recently used expiration to reduce
22
+ memory use. The cache is also self adjusting and will expand as needed
23
+ for better performance. It also handles Hash keys and string values
24
+ with two options, `:cache_keys`, a boolean and `:cache_str` an
25
+ integer. The `:cache_str` if set to more than zero is the limit for
26
+ the length of string values to cache. The maximum value is 35 which
27
+ allows strings up to 34 bytes to be cached.
28
+
29
+ One interesting aspect of the cache is not so much the string caching
30
+ which performs similar to the Ruby intern functions but the caching of
31
+ symbols and object attribute names. There is a significant gain for
32
+ symbols and object attributes.
33
+
34
+ If the cache is not desired then setting the default options to turn
35
+ it off can be done with this line:
36
+
37
+ ``` ruby
38
+ Oj.default_options = { cache_keys: false, cache_str: 0 }
39
+ ```
40
+
41
+ ### Oj::Parser
42
+
43
+ The new parser uses a different core that follows the approach taken
44
+ by [OjC](https://github.com/ohler55/ojc) and
45
+ [OjG](https://github.com/ohler55/ojg). It also takes advantage of the
46
+ bulk Array and Hash functions. Another issue the new parser addresses
47
+ is option management. Instead of a single global default_options each
48
+ parser instance maintains it's own options.
49
+
50
+ There is a price to be paid when using the Oj::Parser. The API is not
51
+ the same the older parser. A single parser can only be used in a
52
+ single thread. This allows reuse of internal buffers for additional
53
+ improvements in performance.
54
+
55
+ The performane advantage of the Oj::Parse is that it is more than 3
56
+ times faster than the Oj::compat_load call and 6 times faster than the
57
+ JSON gem.
58
+
59
+ ### Dump Performance
60
+
61
+ Thanks to Watson1978 Oj.dump also received a speed boost.
data/ext/oj/buf.h CHANGED
@@ -1,103 +1,84 @@
1
- /* buf.h
2
- * Copyright (c) 2011, Peter Ohler
3
- * All rights reserved.
4
- *
5
- * Redistribution and use in source and binary forms, with or without
6
- * modification, are permitted provided that the following conditions are met:
7
- *
8
- * - Redistributions of source code must retain the above copyright notice, this
9
- * list of conditions and the following disclaimer.
10
- *
11
- * - Redistributions in binary form must reproduce the above copyright notice,
12
- * this list of conditions and the following disclaimer in the documentation
13
- * and/or other materials provided with the distribution.
14
- *
15
- * - Neither the name of Peter Ohler nor the names of its contributors may be
16
- * used to endorse or promote products derived from this software without
17
- * specific prior written permission.
18
- *
19
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
- */
1
+ // Copyright (c) 2011 Peter Ohler. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file in the project root for license details.
30
3
 
31
- #ifndef __OJ_BUF_H__
32
- #define __OJ_BUF_H__
4
+ #ifndef OJ_BUF_H
5
+ #define OJ_BUF_H
33
6
 
34
7
  #include "ruby.h"
35
8
 
36
- typedef struct _Buf {
37
- char *head;
38
- char *end;
39
- char *tail;
40
- char base[1024];
41
- } *Buf;
9
+ typedef struct _buf {
10
+ char *head;
11
+ char *end;
12
+ char *tail;
13
+ char base[1024];
14
+ } * Buf;
42
15
 
43
- inline static void
44
- buf_init(Buf buf) {
16
+ inline static void buf_init(Buf buf) {
45
17
  buf->head = buf->base;
46
- buf->end = buf->base + sizeof(buf->base) - 1;
18
+ buf->end = buf->base + sizeof(buf->base) - 1;
47
19
  buf->tail = buf->head;
48
20
  }
49
21
 
50
- inline static void
51
- buf_cleanup(Buf buf) {
22
+ inline static void buf_reset(Buf buf) {
23
+ buf->tail = buf->head;
24
+ }
25
+
26
+ inline static void buf_cleanup(Buf buf) {
52
27
  if (buf->base != buf->head) {
53
28
  xfree(buf->head);
54
29
  }
55
30
  }
56
31
 
57
- inline static size_t
58
- buf_len(Buf buf) {
32
+ inline static size_t buf_len(Buf buf) {
59
33
  return buf->tail - buf->head;
60
34
  }
61
35
 
62
- inline static void
63
- buf_append_string(Buf buf, const char *s, size_t slen) {
36
+ inline static const char *buf_str(Buf buf) {
37
+ *buf->tail = '\0';
38
+ return buf->head;
39
+ }
40
+
41
+ inline static void buf_append_string(Buf buf, const char *s, size_t slen) {
42
+ if (0 == slen) {
43
+ return;
44
+ }
45
+
64
46
  if (buf->end <= buf->tail + slen) {
65
- size_t len = buf->end - buf->head;
66
- size_t toff = buf->tail - buf->head;
67
- size_t new_len = len + slen + len / 2;
47
+ size_t len = buf->end - buf->head;
48
+ size_t toff = buf->tail - buf->head;
49
+ size_t new_len = len + slen + len / 2;
68
50
 
69
- if (buf->base == buf->head) {
70
- buf->head = ALLOC_N(char, new_len);
71
- memcpy(buf->head, buf->base, len);
72
- } else {
73
- REALLOC_N(buf->head, char, new_len);
74
- }
75
- buf->tail = buf->head + toff;
76
- buf->end = buf->head + new_len - 1;
51
+ if (buf->base == buf->head) {
52
+ buf->head = ALLOC_N(char, new_len);
53
+ memcpy(buf->head, buf->base, len);
54
+ } else {
55
+ REALLOC_N(buf->head, char, new_len);
56
+ }
57
+ buf->tail = buf->head + toff;
58
+ buf->end = buf->head + new_len - 1;
77
59
  }
78
60
  memcpy(buf->tail, s, slen);
79
61
  buf->tail += slen;
80
62
  }
81
-
82
- inline static void
83
- buf_append(Buf buf, char c) {
63
+
64
+ inline static void buf_append(Buf buf, char c) {
84
65
  if (buf->end <= buf->tail) {
85
- size_t len = buf->end - buf->head;
86
- size_t toff = buf->tail - buf->head;
87
- size_t new_len = len + len / 2;
66
+ size_t len = buf->end - buf->head;
67
+ size_t toff = buf->tail - buf->head;
68
+ size_t new_len = len + len / 2;
88
69
 
89
- if (buf->base == buf->head) {
90
- buf->head = ALLOC_N(char, new_len);
91
- memcpy(buf->head, buf->base, len);
92
- } else {
93
- REALLOC_N(buf->head, char, new_len);
94
- }
95
- buf->tail = buf->head + toff;
96
- buf->end = buf->head + new_len - 1;
70
+ if (buf->base == buf->head) {
71
+ buf->head = ALLOC_N(char, new_len);
72
+ memcpy(buf->head, buf->base, len);
73
+ } else {
74
+ REALLOC_N(buf->head, char, new_len);
75
+ }
76
+ buf->tail = buf->head + toff;
77
+ buf->end = buf->head + new_len - 1;
97
78
  }
98
79
  *buf->tail = c;
99
80
  buf->tail++;
100
81
  //*buf->tail = '\0'; // for debugging
101
82
  }
102
83
 
103
- #endif /* __OJ_BUF_H__ */
84
+ #endif /* OJ_BUF_H */
data/ext/oj/cache.c ADDED
@@ -0,0 +1,326 @@
1
+ // Copyright (c) 2011, 2021 Peter Ohler. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file in the project root for license details.
3
+
4
+ #if HAVE_PTHREAD_MUTEX_INIT
5
+ #include <pthread.h>
6
+ #endif
7
+ #include <stdlib.h>
8
+
9
+ #include "cache.h"
10
+
11
+ // The stdlib calloc, realloc, and free are used instead of the Ruby ALLOC,
12
+ // ALLOC_N, REALLOC, and xfree since the later could trigger a GC which will
13
+ // either corrupt memory or if the mark function locks will deadlock.
14
+
15
+ #define REHASH_LIMIT 4
16
+ #define MIN_SHIFT 8
17
+ #define REUSE_MAX 8192
18
+
19
+ #if HAVE_PTHREAD_MUTEX_INIT
20
+ #define CACHE_LOCK(c) pthread_mutex_lock(&((c)->mutex))
21
+ #define CACHE_UNLOCK(c) pthread_mutex_unlock(&((c)->mutex))
22
+ #else
23
+ #define CACHE_LOCK(c) rb_mutex_lock((c)->mutex)
24
+ #define CACHE_UNLOCK(c) rb_mutex_unlock((c)->mutex)
25
+ #endif
26
+
27
+ // almost the Murmur hash algorithm
28
+ #define M 0x5bd1e995
29
+
30
+ typedef struct _slot {
31
+ struct _slot * next;
32
+ VALUE val;
33
+ uint64_t hash;
34
+ volatile uint32_t use_cnt;
35
+ uint8_t klen;
36
+ char key[CACHE_MAX_KEY];
37
+ } * Slot;
38
+
39
+ typedef struct _cache {
40
+ volatile Slot * slots;
41
+ volatile size_t cnt;
42
+ VALUE (*form)(const char *str, size_t len);
43
+ uint64_t size;
44
+ uint64_t mask;
45
+ VALUE (*intern)(struct _cache *c, const char *key, size_t len);
46
+ volatile Slot reuse;
47
+ size_t rcnt;
48
+ #if HAVE_PTHREAD_MUTEX_INIT
49
+ pthread_mutex_t mutex;
50
+ #else
51
+ VALUE mutex;
52
+ #endif
53
+ uint8_t xrate;
54
+ bool mark;
55
+ } * Cache;
56
+
57
+ void cache_set_form(Cache c, VALUE (*form)(const char *str, size_t len)) {
58
+ c->form = form;
59
+ }
60
+
61
+ static uint64_t hash_calc(const uint8_t *key, size_t len) {
62
+ const uint8_t *end = key + len;
63
+ const uint8_t *endless = key + (len & 0xFFFFFFFC);
64
+ uint64_t h = (uint64_t)len;
65
+ uint64_t k;
66
+
67
+ while (key < endless) {
68
+ k = (uint64_t)*key++;
69
+ k |= (uint64_t)*key++ << 8;
70
+ k |= (uint64_t)*key++ << 16;
71
+ k |= (uint64_t)*key++ << 24;
72
+
73
+ k *= M;
74
+ k ^= k >> 24;
75
+ h *= M;
76
+ h ^= k * M;
77
+ }
78
+ if (1 < end - key) {
79
+ uint16_t k16 = (uint16_t)*key++;
80
+
81
+ k16 |= (uint16_t)*key++ << 8;
82
+ h ^= k16 << 8;
83
+ }
84
+ if (key < end) {
85
+ h ^= *key;
86
+ }
87
+ h *= M;
88
+ h ^= h >> 13;
89
+ h *= M;
90
+ h ^= h >> 15;
91
+
92
+ return h;
93
+ }
94
+
95
+ static void rehash(Cache c) {
96
+ uint64_t osize;
97
+ Slot * end;
98
+ Slot * sp;
99
+
100
+ osize = c->size;
101
+ c->size = osize * 4;
102
+ c->mask = c->size - 1;
103
+ c->slots = realloc((void *)c->slots, sizeof(Slot) * c->size);
104
+ memset((Slot *)c->slots + osize, 0, sizeof(Slot) * osize * 3);
105
+ end = (Slot *)c->slots + osize;
106
+ for (sp = (Slot *)c->slots; sp < end; sp++) {
107
+ Slot s = *sp;
108
+ Slot next = NULL;
109
+
110
+ *sp = NULL;
111
+ for (; NULL != s; s = next) {
112
+ uint64_t h = s->hash & c->mask;
113
+ Slot * bucket = (Slot *)c->slots + h;
114
+
115
+ next = s->next;
116
+ s->next = *bucket;
117
+ *bucket = s;
118
+ }
119
+ }
120
+ }
121
+
122
+ static VALUE lockless_intern(Cache c, const char *key, size_t len) {
123
+ uint64_t h = hash_calc((const uint8_t *)key, len);
124
+ Slot * bucket = (Slot *)c->slots + (h & c->mask);
125
+ Slot b;
126
+ volatile VALUE rkey;
127
+
128
+ while (REUSE_MAX < c->rcnt) {
129
+ if (NULL != (b = c->reuse)) {
130
+ c->reuse = b->next;
131
+ free(b);
132
+ c->rcnt--;
133
+ } else {
134
+ // An accounting error occured somewhere so correct it.
135
+ c->rcnt = 0;
136
+ }
137
+ }
138
+ for (b = *bucket; NULL != b; b = b->next) {
139
+ if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
140
+ b->use_cnt += 16;
141
+ return b->val;
142
+ }
143
+ }
144
+ rkey = c->form(key, len);
145
+ if (NULL == (b = c->reuse)) {
146
+ b = calloc(1, sizeof(struct _slot));
147
+ } else {
148
+ c->reuse = b->next;
149
+ c->rcnt--;
150
+ }
151
+ b->hash = h;
152
+ memcpy(b->key, key, len);
153
+ b->klen = (uint8_t)len;
154
+ b->key[len] = '\0';
155
+ b->val = rkey;
156
+ b->use_cnt = 4;
157
+ b->next = *bucket;
158
+ *bucket = b;
159
+ c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated.
160
+ if (REHASH_LIMIT < c->cnt / c->size) {
161
+ rehash(c);
162
+ }
163
+ return rkey;
164
+ }
165
+
166
+ static VALUE locking_intern(Cache c, const char *key, size_t len) {
167
+ uint64_t h;
168
+ Slot * bucket;
169
+ Slot b;
170
+ uint64_t old_size;
171
+ volatile VALUE rkey;
172
+
173
+ CACHE_LOCK(c);
174
+ while (REUSE_MAX < c->rcnt) {
175
+ if (NULL != (b = c->reuse)) {
176
+ c->reuse = b->next;
177
+ free(b);
178
+ c->rcnt--;
179
+ } else {
180
+ // An accounting error occured somewhere so correct it.
181
+ c->rcnt = 0;
182
+ }
183
+ }
184
+ h = hash_calc((const uint8_t *)key, len);
185
+ bucket = (Slot *)c->slots + (h & c->mask);
186
+ for (b = *bucket; NULL != b; b = b->next) {
187
+ if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
188
+ b->use_cnt += 4;
189
+ CACHE_UNLOCK(c);
190
+
191
+ return b->val;
192
+ }
193
+ }
194
+ old_size = c->size;
195
+ // The creation of a new value may trigger a GC which be a problem if the
196
+ // cache is locked so make sure it is unlocked for the key value creation.
197
+ if (NULL != (b = c->reuse)) {
198
+ c->reuse = b->next;
199
+ c->rcnt--;
200
+ }
201
+ CACHE_UNLOCK(c);
202
+ if (NULL == b) {
203
+ b = calloc(1, sizeof(struct _slot));
204
+ }
205
+ rkey = c->form(key, len);
206
+ b->hash = h;
207
+ memcpy(b->key, key, len);
208
+ b->klen = (uint8_t)len;
209
+ b->key[len] = '\0';
210
+ b->val = rkey;
211
+ b->use_cnt = 16;
212
+
213
+ // Lock again to add the new entry.
214
+ CACHE_LOCK(c);
215
+ if (old_size != c->size) {
216
+ h = hash_calc((const uint8_t *)key, len);
217
+ bucket = (Slot *)c->slots + (h & c->mask);
218
+ }
219
+ b->next = *bucket;
220
+ *bucket = b;
221
+ c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated.
222
+ if (REHASH_LIMIT < c->cnt / c->size) {
223
+ rehash(c);
224
+ }
225
+ CACHE_UNLOCK(c);
226
+
227
+ return rkey;
228
+ }
229
+
230
+ Cache cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking) {
231
+ Cache c = calloc(1, sizeof(struct _cache));
232
+ int shift = 0;
233
+
234
+ for (; REHASH_LIMIT < size; size /= 2, shift++) {
235
+ }
236
+ if (shift < MIN_SHIFT) {
237
+ shift = MIN_SHIFT;
238
+ }
239
+ #if HAVE_PTHREAD_MUTEX_INIT
240
+ pthread_mutex_init(&c->mutex, NULL);
241
+ #else
242
+ c->mutex = rb_mutex_new();
243
+ #endif
244
+ c->size = 1 << shift;
245
+ c->mask = c->size - 1;
246
+ c->slots = calloc(c->size, sizeof(Slot));
247
+ c->form = form;
248
+ c->xrate = 1; // low
249
+ c->mark = mark;
250
+ if (locking) {
251
+ c->intern = locking_intern;
252
+ } else {
253
+ c->intern = lockless_intern;
254
+ }
255
+ return c;
256
+ }
257
+
258
+ void cache_set_expunge_rate(Cache c, int rate) {
259
+ c->xrate = (uint8_t)rate;
260
+ }
261
+
262
+ void cache_free(Cache c) {
263
+ uint64_t i;
264
+
265
+ for (i = 0; i < c->size; i++) {
266
+ Slot next;
267
+ Slot s;
268
+
269
+ for (s = c->slots[i]; NULL != s; s = next) {
270
+ next = s->next;
271
+ free(s);
272
+ }
273
+ }
274
+ free((void *)c->slots);
275
+ free(c);
276
+ }
277
+
278
+ void cache_mark(Cache c) {
279
+ uint64_t i;
280
+
281
+ #if !HAVE_PTHREAD_MUTEX_INIT
282
+ rb_gc_mark(c->mutex);
283
+ #endif
284
+ if (0 == c->cnt) {
285
+ return;
286
+ }
287
+ for (i = 0; i < c->size; i++) {
288
+ Slot s;
289
+ Slot prev = NULL;
290
+ Slot next;
291
+
292
+ for (s = c->slots[i]; NULL != s; s = next) {
293
+ next = s->next;
294
+ if (0 == s->use_cnt) {
295
+ if (NULL == prev) {
296
+ c->slots[i] = next;
297
+ } else {
298
+ prev->next = next;
299
+ }
300
+ c->cnt--;
301
+ s->next = c->reuse;
302
+ c->reuse = s;
303
+ c->rcnt++;
304
+ continue;
305
+ }
306
+ switch (c->xrate) {
307
+ case 0: break;
308
+ case 2: s->use_cnt -= 2; break;
309
+ case 3: s->use_cnt /= 2; break;
310
+ default: s->use_cnt--; break;
311
+ }
312
+ if (c->mark) {
313
+ rb_gc_mark(s->val);
314
+ }
315
+ prev = s;
316
+ }
317
+ }
318
+ }
319
+
320
+ VALUE
321
+ cache_intern(Cache c, const char *key, size_t len) {
322
+ if (CACHE_MAX_KEY <= len) {
323
+ return c->form(key, len);
324
+ }
325
+ return c->intern(c, key, len);
326
+ }
data/ext/oj/cache.h ADDED
@@ -0,0 +1,21 @@
1
+ // Copyright (c) 2021 Peter Ohler. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file in the project root for license details.
3
+
4
+ #ifndef CACHE_H
5
+ #define CACHE_H
6
+
7
+ #include <ruby.h>
8
+ #include <stdbool.h>
9
+
10
+ #define CACHE_MAX_KEY 35
11
+
12
+ struct _cache;
13
+
14
+ extern struct _cache *cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking);
15
+ extern void cache_free(struct _cache *c);
16
+ extern void cache_mark(struct _cache *c);
17
+ extern void cache_set_form(struct _cache *c, VALUE (*form)(const char *str, size_t len));
18
+ extern VALUE cache_intern(struct _cache *c, const char *key, size_t len);
19
+ extern void cache_set_expunge_rate(struct _cache *c, int rate);
20
+
21
+ #endif /* CACHE_H */