oj_windows 3.16.15

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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +44 -0
  3. data/LICENSE +21 -0
  4. data/README.md +164 -0
  5. data/ext/oj_windows/buf.h +85 -0
  6. data/ext/oj_windows/cache.c +339 -0
  7. data/ext/oj_windows/cache.h +22 -0
  8. data/ext/oj_windows/cache8.c +105 -0
  9. data/ext/oj_windows/cache8.h +21 -0
  10. data/ext/oj_windows/circarray.c +64 -0
  11. data/ext/oj_windows/circarray.h +22 -0
  12. data/ext/oj_windows/code.c +214 -0
  13. data/ext/oj_windows/code.h +40 -0
  14. data/ext/oj_windows/compat.c +239 -0
  15. data/ext/oj_windows/custom.c +1074 -0
  16. data/ext/oj_windows/debug.c +126 -0
  17. data/ext/oj_windows/dump.c +1556 -0
  18. data/ext/oj_windows/dump.h +110 -0
  19. data/ext/oj_windows/dump_compat.c +901 -0
  20. data/ext/oj_windows/dump_leaf.c +162 -0
  21. data/ext/oj_windows/dump_object.c +710 -0
  22. data/ext/oj_windows/dump_strict.c +405 -0
  23. data/ext/oj_windows/encode.h +16 -0
  24. data/ext/oj_windows/err.c +57 -0
  25. data/ext/oj_windows/err.h +67 -0
  26. data/ext/oj_windows/extconf.rb +77 -0
  27. data/ext/oj_windows/fast.c +1710 -0
  28. data/ext/oj_windows/intern.c +325 -0
  29. data/ext/oj_windows/intern.h +22 -0
  30. data/ext/oj_windows/mem.c +320 -0
  31. data/ext/oj_windows/mem.h +53 -0
  32. data/ext/oj_windows/mimic_json.c +919 -0
  33. data/ext/oj_windows/object.c +726 -0
  34. data/ext/oj_windows/odd.c +245 -0
  35. data/ext/oj_windows/odd.h +43 -0
  36. data/ext/oj_windows/oj.c +2097 -0
  37. data/ext/oj_windows/oj.h +420 -0
  38. data/ext/oj_windows/parse.c +1317 -0
  39. data/ext/oj_windows/parse.h +113 -0
  40. data/ext/oj_windows/parser.c +1600 -0
  41. data/ext/oj_windows/parser.h +103 -0
  42. data/ext/oj_windows/rails.c +1484 -0
  43. data/ext/oj_windows/rails.h +18 -0
  44. data/ext/oj_windows/reader.c +222 -0
  45. data/ext/oj_windows/reader.h +137 -0
  46. data/ext/oj_windows/resolve.c +80 -0
  47. data/ext/oj_windows/resolve.h +12 -0
  48. data/ext/oj_windows/rxclass.c +144 -0
  49. data/ext/oj_windows/rxclass.h +26 -0
  50. data/ext/oj_windows/saj.c +675 -0
  51. data/ext/oj_windows/saj2.c +584 -0
  52. data/ext/oj_windows/saj2.h +23 -0
  53. data/ext/oj_windows/scp.c +187 -0
  54. data/ext/oj_windows/simd.h +47 -0
  55. data/ext/oj_windows/sparse.c +946 -0
  56. data/ext/oj_windows/stream_writer.c +329 -0
  57. data/ext/oj_windows/strict.c +189 -0
  58. data/ext/oj_windows/string_writer.c +517 -0
  59. data/ext/oj_windows/trace.c +72 -0
  60. data/ext/oj_windows/trace.h +55 -0
  61. data/ext/oj_windows/usual.c +1218 -0
  62. data/ext/oj_windows/usual.h +69 -0
  63. data/ext/oj_windows/util.c +136 -0
  64. data/ext/oj_windows/util.h +20 -0
  65. data/ext/oj_windows/val_stack.c +101 -0
  66. data/ext/oj_windows/val_stack.h +151 -0
  67. data/ext/oj_windows/validate.c +46 -0
  68. data/ext/oj_windows/wab.c +584 -0
  69. data/lib/oj/active_support_helper.rb +39 -0
  70. data/lib/oj/bag.rb +95 -0
  71. data/lib/oj/easy_hash.rb +52 -0
  72. data/lib/oj/error.rb +21 -0
  73. data/lib/oj/json.rb +188 -0
  74. data/lib/oj/mimic.rb +301 -0
  75. data/lib/oj/saj.rb +80 -0
  76. data/lib/oj/schandler.rb +143 -0
  77. data/lib/oj/state.rb +135 -0
  78. data/lib/oj/version.rb +4 -0
  79. data/lib/oj_windows/active_support_helper.rb +39 -0
  80. data/lib/oj_windows/bag.rb +95 -0
  81. data/lib/oj_windows/easy_hash.rb +52 -0
  82. data/lib/oj_windows/error.rb +21 -0
  83. data/lib/oj_windows/json.rb +188 -0
  84. data/lib/oj_windows/mimic.rb +301 -0
  85. data/lib/oj_windows/saj.rb +80 -0
  86. data/lib/oj_windows/schandler.rb +143 -0
  87. data/lib/oj_windows/state.rb +135 -0
  88. data/lib/oj_windows/version.rb +4 -0
  89. data/lib/oj_windows.rb +15 -0
  90. data/pages/Advanced.md +38 -0
  91. data/pages/Compatibility.md +49 -0
  92. data/pages/Custom.md +37 -0
  93. data/pages/Encoding.md +61 -0
  94. data/pages/InstallOptions.md +20 -0
  95. data/pages/JsonGem.md +60 -0
  96. data/pages/Modes.md +94 -0
  97. data/pages/Options.md +339 -0
  98. data/pages/Parser.md +134 -0
  99. data/pages/Rails.md +85 -0
  100. data/pages/Security.md +43 -0
  101. data/pages/WAB.md +12 -0
  102. metadata +242 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 608dd07735fb09d340eac0f8adc2d9bce84cf1b0bfea863f74ef9c5ff26dd322
4
+ data.tar.gz: 11920df4ad7ba7696f979c35aa54ce383d5855b99c07e9449aa7a1c6f62ba6d3
5
+ SHA512:
6
+ metadata.gz: 1975faac825fcf5dd9f851829dcbea96e4f0487a678161841f630a3a97a4f3acceadedfbbbc379ac01642c0c2d08c3a60f8a7f405f4f5c5fb1a02c097cf12d64
7
+ data.tar.gz: 249dbbe5e901655c192c94453aac166f45af7da6d84effbaf40935ce262d6346ffbe81cf78085f0491fafa35cd01ca0131bb0184f658f416faaebf3b8b61a4f1
data/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to `oj_windows` will be documented in this file.
4
+
5
+ ## [3.16.14] - 2025-12-20
6
+
7
+ ### Initial Release
8
+
9
+ First production-ready release of `oj_windows`, a Windows-exclusive fork optimized for Ruby 3.4.8 MSVC.
10
+
11
+ #### C Extension Modernization
12
+ - Replaced `pthread` mutexes with Windows `CRITICAL_SECTION`
13
+ - Replaced `timegm` with `_mkgmtime` for MSVC compatibility
14
+ - Guarded all POSIX headers (`unistd.h`, `sys/types.h`, `poll.h`)
15
+ - Resolved `NAN`/`HUGE_VAL` math macro incompatibilities for MSVC
16
+ - Removed all GCC/Clang specific compiler flags
17
+
18
+ #### Build System
19
+ - Updated `extconf.rb` for MSVC/NMake build process
20
+ - Updated `Rakefile` to remove Unix-specific logic
21
+ - Created `package_install.bat` for Windows build workflow
22
+
23
+ #### Gem Structure
24
+ - Refactored internal paths from `oj` to `oj_windows`
25
+ - Created `lib/oj/` compatibility layer for legacy `rb_require` calls
26
+ - Updated gemspec for Windows-only distribution
27
+
28
+ #### Testing
29
+ - Converted shell test scripts to Windows batch files
30
+ - Fixed test regex patterns for Windows path formats
31
+ - Verified 100% pass rate (373 runs, 0 failures)
32
+
33
+ #### Documentation
34
+ - Cleaned up all documentation for Windows-only usage
35
+ - Removed cross-platform references
36
+ - Added test results table to README
37
+
38
+ ### Test Results Summary
39
+
40
+ ```
41
+ Total: 373 runs, 8,860 assertions, 0 failures, 0 errors, 6 skips
42
+ ```
43
+
44
+ The 6 skipped tests are fork-based operations not supported on Windows.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2012 Peter Ohler
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
13
+ all 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 NONINFRINGEMENT. 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # oj_windows
2
+
3
+ **A Windows-native, high-performance JSON parser and Object marshaller for Ruby.**
4
+
5
+ [![Gem Version](https://img.shields.io/gem/v/oj_windows.svg)](https://rubygems.org/gems/oj_windows)
6
+
7
+ ## Overview
8
+
9
+ `oj_windows` is a production-ready, Windows-exclusive fork optimized for the **Ruby 3.4.8 MSVC** environment. All POSIX-specific code has been removed or replaced with native Windows equivalents, ensuring maximum performance and stability on Windows systems.
10
+
11
+ ## Requirements
12
+
13
+ | Component | Version |
14
+ |-----------|---------|
15
+ | Ruby | 3.4.8+ (MSVC build) |
16
+ | OS | Windows 10/11 x64 |
17
+ | Compiler | Visual Studio 2022+ |
18
+ | Architecture | x64-mswin64_140 |
19
+
20
+ ## Installation
21
+
22
+ ```powershell
23
+ gem install oj_windows
24
+ ```
25
+
26
+ Or in your Gemfile:
27
+
28
+ ```ruby
29
+ gem 'oj_windows'
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```ruby
35
+ require 'oj_windows'
36
+
37
+ # Dump Ruby objects to JSON
38
+ hash = { 'name' => 'oj_windows', 'version' => '3.16.14', 'windows' => true }
39
+ json = Oj.dump(hash)
40
+ # => {"name":"oj_windows","version":"3.16.14","windows":true}
41
+
42
+ # Load JSON back to Ruby objects
43
+ data = Oj.load(json)
44
+ # => {"name"=>"oj_windows", "version"=>"3.16.14", "windows"=>true}
45
+
46
+ # Pretty print
47
+ puts Oj.dump(hash, indent: 2)
48
+ ```
49
+
50
+ ## Features
51
+
52
+ - **High Performance**: Optimized C extension compiled with MSVC
53
+ - **Multiple Modes**: Strict, Compat, Object, Custom, Rails, WAB, Null
54
+ - **JSON Gem Compatibility**: Drop-in replacement via `Oj.mimic_JSON`
55
+ - **Rails Integration**: Full ActiveSupport/ActiveRecord support
56
+ - **Streaming**: SAJ and SC parsers for large documents
57
+ - **StringWriter/StreamWriter**: Efficient JSON generation
58
+
59
+ ## Test Results
60
+
61
+ All tests pass on Windows MSVC Ruby 3.4.8:
62
+
63
+ | Test Suite | Runs | Assertions | Failures | Errors | Skips |
64
+ |------------|------|------------|----------|--------|-------|
65
+ | test_various.rb | 69 | 8,202 | 0 | 0 | 1 |
66
+ | test_strict.rb | 42 | 71 | 0 | 0 | 0 |
67
+ | test_compat.rb | 55 | 112 | 0 | 0 | 0 |
68
+ | test_object.rb | 74 | 160 | 0 | 0 | 0 |
69
+ | test_fast.rb | 43 | 137 | 0 | 0 | 0 |
70
+ | test_saj.rb | 13 | 18 | 0 | 0 | 0 |
71
+ | test_scp.rb | 23 | 23 | 0 | 0 | 2 |
72
+ | test_gc.rb | 3 | 50 | 0 | 0 | 0 |
73
+ | test_writer.rb | 27 | 28 | 0 | 0 | 1 |
74
+ | test_file.rb | 21 | 52 | 0 | 0 | 1 |
75
+ | test_hash.rb | 3 | 7 | 0 | 0 | 0 |
76
+ | **Total** | **373** | **8,860** | **0** | **0** | **6** |
77
+
78
+ > **Note**: The 6 skipped tests are fork-based operations that are not supported on Windows. This is expected behavior.
79
+
80
+ ## Performance Benchmarks
81
+
82
+ Benchmarks run on Windows with Ruby 3.4.8 MSVC (50,000 iterations):
83
+
84
+ ### Parse Performance
85
+ | Parser | Ops/sec |
86
+ |--------|---------|
87
+ | JSON::Ext | 444,501 |
88
+ | Oj:wab | 402,185 |
89
+ | Oj:strict | 380,664 |
90
+
91
+ ### Dump Performance
92
+ | Dumper | Ops/sec |
93
+ |--------|---------|
94
+ | JSON::Ext | 832,994 |
95
+ | Oj:strict | 816,299 |
96
+
97
+ > **\*** In synthetic benchmarks with simple data structures, Ruby's native `JSON::Ext` may appear faster because it is tightly integrated with the Ruby VM. However, `oj_windows` provides significant advantages:
98
+ > - **Object mode**: Full Ruby object serialization/deserialization (not possible with JSON gem)
99
+ > - **Rails/ActiveSupport**: Optimized `as_json`/`to_json` integration
100
+ > - **Streaming parsers**: SAJ and SCP for memory-efficient parsing of large documents
101
+ > - **Circular references**: Automatic detection and handling
102
+ > - **Custom behaviors**: Fine-grained control over serialization
103
+ >
104
+ > For complex real-world applications with deep object graphs, custom types, and Rails integration, `oj_windows` remains the optimal choice.
105
+
106
+ ## Modes
107
+
108
+ | Mode | Description |
109
+ |------|-------------|
110
+ | `:strict` | Strict JSON compliance, only native types |
111
+ | `:compat` | Compatible with the JSON gem |
112
+ | `:object` | Full Ruby object serialization |
113
+ | `:custom` | Customizable serialization behavior |
114
+ | `:rails` | Rails and ActiveSupport compatibility |
115
+ | `:wab` | WAB (Web Application Builder) format |
116
+ | `:null` | Return null for unsupported types |
117
+
118
+ ## Configuration
119
+
120
+ ```ruby
121
+ # Set default options
122
+ Oj.default_options = {
123
+ mode: :compat,
124
+ symbol_keys: true,
125
+ indent: 2
126
+ }
127
+
128
+ # Check current options
129
+ Oj.default_options
130
+ ```
131
+
132
+ ## Documentation
133
+
134
+ - [Options](pages/Options.md) - Parse and dump options
135
+ - [Modes](pages/Modes.md) - Detailed mode documentation
136
+ - [Rails](pages/Rails.md) - Rails integration guide
137
+ - [JsonGem](pages/JsonGem.md) - JSON gem compatibility
138
+ - [Advanced](pages/Advanced.md) - Advanced features
139
+
140
+ ## Building from Source
141
+
142
+ ```powershell
143
+ # Requires MSVC environment
144
+ call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
145
+
146
+ # Build and install
147
+ .\package_install.bat
148
+ ```
149
+
150
+ ## Running Tests
151
+
152
+ ```powershell
153
+ cd test
154
+ .\test_all_no_rails.bat
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT License - See [LICENSE](LICENSE) for details.
160
+
161
+ ---
162
+
163
+ *Version 3.16.14 - December 2025*
164
+ *Original OJ https://github.com/ohler55/oj*
@@ -0,0 +1,85 @@
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.
3
+
4
+ #ifndef OJ_BUF_H
5
+ #define OJ_BUF_H
6
+
7
+ #include "mem.h"
8
+ #include "ruby.h"
9
+
10
+ typedef struct _buf {
11
+ char *head;
12
+ char *end;
13
+ char *tail;
14
+ char base[1024];
15
+ } *Buf;
16
+
17
+ inline static void buf_init(Buf buf) {
18
+ buf->head = buf->base;
19
+ buf->end = buf->base + sizeof(buf->base) - 1;
20
+ buf->tail = buf->head;
21
+ }
22
+
23
+ inline static void buf_reset(Buf buf) {
24
+ buf->tail = buf->head;
25
+ }
26
+
27
+ inline static void buf_cleanup(Buf buf) {
28
+ if (buf->base != buf->head) {
29
+ OJ_R_FREE(buf->head);
30
+ }
31
+ }
32
+
33
+ inline static size_t buf_len(Buf buf) {
34
+ return buf->tail - buf->head;
35
+ }
36
+
37
+ inline static const char *buf_str(Buf buf) {
38
+ *buf->tail = '\0';
39
+ return buf->head;
40
+ }
41
+
42
+ inline static void buf_append_string(Buf buf, const char *s, size_t slen) {
43
+ if (0 == slen) {
44
+ return;
45
+ }
46
+
47
+ if (buf->end <= buf->tail + slen) {
48
+ size_t len = buf->end - buf->head;
49
+ size_t toff = buf->tail - buf->head;
50
+ size_t new_len = len + slen + len / 2;
51
+
52
+ if (buf->base == buf->head) {
53
+ buf->head = OJ_R_ALLOC_N(char, new_len);
54
+ memcpy(buf->head, buf->base, len);
55
+ } else {
56
+ OJ_R_REALLOC_N(buf->head, char, new_len);
57
+ }
58
+ buf->tail = buf->head + toff;
59
+ buf->end = buf->head + new_len - 1;
60
+ }
61
+ memcpy(buf->tail, s, slen);
62
+ buf->tail += slen;
63
+ }
64
+
65
+ inline static void buf_append(Buf buf, char c) {
66
+ if (buf->end <= buf->tail) {
67
+ size_t len = buf->end - buf->head;
68
+ size_t toff = buf->tail - buf->head;
69
+ size_t new_len = len + len / 2;
70
+
71
+ if (buf->base == buf->head) {
72
+ buf->head = OJ_R_ALLOC_N(char, new_len);
73
+ memcpy(buf->head, buf->base, len);
74
+ } else {
75
+ OJ_R_REALLOC_N(buf->head, char, new_len);
76
+ }
77
+ buf->tail = buf->head + toff;
78
+ buf->end = buf->head + new_len - 1;
79
+ }
80
+ *buf->tail = c;
81
+ buf->tail++;
82
+ //*buf->tail = '\0'; // for debugging
83
+ }
84
+
85
+ #endif /* OJ_BUF_H */
@@ -0,0 +1,339 @@
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
+ #include "mem.h"
11
+
12
+ // The stdlib calloc, realloc, and free are used instead of the Ruby ALLOC,
13
+ // ALLOC_N, REALLOC, and xfree since the later could trigger a GC which will
14
+ // either corrupt memory or if the mark function locks will deadlock.
15
+
16
+ #define REHASH_LIMIT 4
17
+ #define MIN_SHIFT 8
18
+ #define REUSE_MAX 8192
19
+
20
+ #if defined(_WIN32)
21
+ #define CACHE_LOCK(c) EnterCriticalSection(&((c)->cs))
22
+ #define CACHE_UNLOCK(c) LeaveCriticalSection(&((c)->cs))
23
+ #elif HAVE_PTHREAD_MUTEX_INIT
24
+ #define CACHE_LOCK(c) pthread_mutex_lock(&((c)->mutex))
25
+ #define CACHE_UNLOCK(c) pthread_mutex_unlock(&((c)->mutex))
26
+ #else
27
+ #define CACHE_LOCK(c) rb_mutex_lock((c)->mutex)
28
+ #define CACHE_UNLOCK(c) rb_mutex_unlock((c)->mutex)
29
+ #endif
30
+
31
+ // almost the Murmur hash algorithm
32
+ #define M 0x5bd1e995
33
+
34
+ typedef struct _slot {
35
+ struct _slot *next;
36
+ VALUE val;
37
+ uint64_t hash;
38
+ volatile uint32_t use_cnt;
39
+ uint8_t klen;
40
+ char key[CACHE_MAX_KEY];
41
+ } *Slot;
42
+
43
+ typedef struct _cache {
44
+ volatile Slot *slots;
45
+ volatile size_t cnt;
46
+ VALUE (*form)(const char *str, size_t len);
47
+ uint64_t size;
48
+ uint64_t mask;
49
+ VALUE (*intern)(struct _cache *c, const char *key, size_t len);
50
+ volatile Slot reuse;
51
+ size_t rcnt;
52
+ #if defined(_WIN32)
53
+ CRITICAL_SECTION cs;
54
+ #elif HAVE_PTHREAD_MUTEX_INIT
55
+ pthread_mutex_t mutex;
56
+ #else
57
+ VALUE mutex;
58
+ #endif
59
+ uint8_t xrate;
60
+ bool mark;
61
+ } *Cache;
62
+
63
+ void cache_set_form(Cache c, VALUE (*form)(const char *str, size_t len)) {
64
+ c->form = form;
65
+ }
66
+
67
+ static uint64_t hash_calc(const uint8_t *key, size_t len) {
68
+ const uint8_t *end = key + len;
69
+ const uint8_t *endless = key + (len & 0xFFFFFFFC);
70
+ uint64_t h = (uint64_t)len;
71
+ uint64_t k;
72
+
73
+ while (key < endless) {
74
+ k = (uint64_t)*key++;
75
+ k |= (uint64_t)*key++ << 8;
76
+ k |= (uint64_t)*key++ << 16;
77
+ k |= (uint64_t)*key++ << 24;
78
+
79
+ k *= M;
80
+ k ^= k >> 24;
81
+ h *= M;
82
+ h ^= k * M;
83
+ }
84
+ if (1 < end - key) {
85
+ uint16_t k16 = (uint16_t)*key++;
86
+
87
+ k16 |= (uint16_t)*key++ << 8;
88
+ h ^= k16 << 8;
89
+ }
90
+ if (key < end) {
91
+ h ^= *key;
92
+ }
93
+ h *= M;
94
+ h ^= h >> 13;
95
+ h *= M;
96
+ h ^= h >> 15;
97
+
98
+ return h;
99
+ }
100
+
101
+ static void rehash(Cache c) {
102
+ uint64_t osize;
103
+ Slot *end;
104
+ Slot *sp;
105
+
106
+ osize = c->size;
107
+ c->size = osize * 4;
108
+ c->mask = c->size - 1;
109
+ c->slots = OJ_REALLOC((void *)c->slots, sizeof(Slot) * c->size);
110
+ memset((Slot *)c->slots + osize, 0, sizeof(Slot) * osize * 3);
111
+ end = (Slot *)c->slots + osize;
112
+ for (sp = (Slot *)c->slots; sp < end; sp++) {
113
+ Slot s = *sp;
114
+ Slot next = NULL;
115
+
116
+ *sp = NULL;
117
+ for (; NULL != s; s = next) {
118
+ uint64_t h = s->hash & c->mask;
119
+ Slot *bucket = (Slot *)c->slots + h;
120
+
121
+ next = s->next;
122
+ s->next = *bucket;
123
+ *bucket = s;
124
+ }
125
+ }
126
+ }
127
+
128
+ static VALUE lockless_intern(Cache c, const char *key, size_t len) {
129
+ uint64_t h = hash_calc((const uint8_t *)key, len);
130
+ Slot *bucket = (Slot *)c->slots + (h & c->mask);
131
+ Slot b;
132
+ volatile VALUE rkey;
133
+
134
+ while (REUSE_MAX < c->rcnt) {
135
+ if (NULL != (b = c->reuse)) {
136
+ c->reuse = b->next;
137
+ OJ_FREE(b);
138
+ c->rcnt--;
139
+ } else {
140
+ // An accounting error occured somewhere so correct it.
141
+ c->rcnt = 0;
142
+ }
143
+ }
144
+ for (b = *bucket; NULL != b; b = b->next) {
145
+ if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
146
+ b->use_cnt += 16;
147
+ return b->val;
148
+ }
149
+ }
150
+ rkey = c->form(key, len);
151
+ if (NULL == (b = c->reuse)) {
152
+ b = OJ_CALLOC(1, sizeof(struct _slot));
153
+ } else {
154
+ c->reuse = b->next;
155
+ c->rcnt--;
156
+ }
157
+ b->hash = h;
158
+ memcpy(b->key, key, len);
159
+ b->klen = (uint8_t)len;
160
+ b->key[len] = '\0';
161
+ b->val = rkey;
162
+ b->use_cnt = 4;
163
+ b->next = *bucket;
164
+ *bucket = b;
165
+ c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated.
166
+ if (REHASH_LIMIT < c->cnt / c->size) {
167
+ rehash(c);
168
+ }
169
+ return rkey;
170
+ }
171
+
172
+ static VALUE locking_intern(Cache c, const char *key, size_t len) {
173
+ uint64_t h;
174
+ Slot *bucket;
175
+ Slot b;
176
+ uint64_t old_size;
177
+ volatile VALUE rkey;
178
+
179
+ CACHE_LOCK(c);
180
+ while (REUSE_MAX < c->rcnt) {
181
+ if (NULL != (b = c->reuse)) {
182
+ c->reuse = b->next;
183
+ OJ_FREE(b);
184
+ c->rcnt--;
185
+ } else {
186
+ // An accounting error occured somewhere so correct it.
187
+ c->rcnt = 0;
188
+ }
189
+ }
190
+ h = hash_calc((const uint8_t *)key, len);
191
+ bucket = (Slot *)c->slots + (h & c->mask);
192
+ for (b = *bucket; NULL != b; b = b->next) {
193
+ if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
194
+ b->use_cnt += 4;
195
+ CACHE_UNLOCK(c);
196
+
197
+ return b->val;
198
+ }
199
+ }
200
+ old_size = c->size;
201
+ // The creation of a new value may trigger a GC which be a problem if the
202
+ // cache is locked so make sure it is unlocked for the key value creation.
203
+ if (NULL != (b = c->reuse)) {
204
+ c->reuse = b->next;
205
+ c->rcnt--;
206
+ }
207
+ CACHE_UNLOCK(c);
208
+ if (NULL == b) {
209
+ b = OJ_CALLOC(1, sizeof(struct _slot));
210
+ }
211
+ rkey = c->form(key, len);
212
+ b->hash = h;
213
+ memcpy(b->key, key, len);
214
+ b->klen = (uint8_t)len;
215
+ b->key[len] = '\0';
216
+ b->val = rkey;
217
+ b->use_cnt = 16;
218
+
219
+ // Lock again to add the new entry.
220
+ CACHE_LOCK(c);
221
+ if (old_size != c->size) {
222
+ h = hash_calc((const uint8_t *)key, len);
223
+ bucket = (Slot *)c->slots + (h & c->mask);
224
+ }
225
+ b->next = *bucket;
226
+ *bucket = b;
227
+ c->cnt++; // Don't worry about wrapping. Worse case is the entry is removed and recreated.
228
+ if (REHASH_LIMIT < c->cnt / c->size) {
229
+ rehash(c);
230
+ }
231
+ CACHE_UNLOCK(c);
232
+
233
+ return rkey;
234
+ }
235
+
236
+ Cache cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking) {
237
+ Cache c = OJ_CALLOC(1, sizeof(struct _cache));
238
+ int shift = 0;
239
+
240
+ for (; REHASH_LIMIT < size; size /= 2, shift++) {
241
+ }
242
+ if (shift < MIN_SHIFT) {
243
+ shift = MIN_SHIFT;
244
+ }
245
+ #if defined(_WIN32)
246
+ InitializeCriticalSection(&c->cs);
247
+ #elif HAVE_PTHREAD_MUTEX_INIT
248
+ pthread_mutex_init(&c->mutex, NULL);
249
+ #else
250
+ c->mutex = rb_mutex_new();
251
+ #endif
252
+ c->size = 1 << shift;
253
+ c->mask = c->size - 1;
254
+ c->slots = OJ_CALLOC(c->size, sizeof(Slot));
255
+ c->form = form;
256
+ c->xrate = 1; // low
257
+ c->mark = mark;
258
+ if (locking) {
259
+ c->intern = locking_intern;
260
+ } else {
261
+ c->intern = lockless_intern;
262
+ }
263
+ return c;
264
+ }
265
+
266
+ void cache_set_expunge_rate(Cache c, int rate) {
267
+ c->xrate = (uint8_t)rate;
268
+ }
269
+
270
+ void cache_free(void *data) {
271
+ Cache c = (Cache)data;
272
+ uint64_t i;
273
+
274
+ for (i = 0; i < c->size; i++) {
275
+ Slot next;
276
+ Slot s;
277
+
278
+ for (s = c->slots[i]; NULL != s; s = next) {
279
+ next = s->next;
280
+ OJ_FREE(s);
281
+ }
282
+ }
283
+ OJ_FREE((void *)c->slots);
284
+ #if defined(_WIN32)
285
+ DeleteCriticalSection(&c->cs);
286
+ #endif
287
+ OJ_FREE(c);
288
+ }
289
+
290
+ void cache_mark(void *data) {
291
+ Cache c = (Cache)data;
292
+ uint64_t i;
293
+
294
+ #if !HAVE_PTHREAD_MUTEX_INIT && !defined(_WIN32)
295
+ rb_gc_mark(c->mutex);
296
+ #endif
297
+ if (0 == c->cnt) {
298
+ return;
299
+ }
300
+ for (i = 0; i < c->size; i++) {
301
+ Slot s;
302
+ Slot prev = NULL;
303
+ Slot next;
304
+
305
+ for (s = c->slots[i]; NULL != s; s = next) {
306
+ next = s->next;
307
+ if (0 == s->use_cnt) {
308
+ if (NULL == prev) {
309
+ c->slots[i] = next;
310
+ } else {
311
+ prev->next = next;
312
+ }
313
+ c->cnt--;
314
+ s->next = c->reuse;
315
+ c->reuse = s;
316
+ c->rcnt++;
317
+ continue;
318
+ }
319
+ switch (c->xrate) {
320
+ case 0: break;
321
+ case 2: s->use_cnt -= 2; break;
322
+ case 3: s->use_cnt /= 2; break;
323
+ default: s->use_cnt--; break;
324
+ }
325
+ if (c->mark) {
326
+ rb_gc_mark(s->val);
327
+ }
328
+ prev = s;
329
+ }
330
+ }
331
+ }
332
+
333
+ VALUE
334
+ cache_intern(Cache c, const char *key, size_t len) {
335
+ if (CACHE_MAX_KEY <= len) {
336
+ return c->form(key, len);
337
+ }
338
+ return c->intern(c, key, len);
339
+ }
@@ -0,0 +1,22 @@
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
+ typedef struct _cache *Cache;
14
+
15
+ extern struct _cache *cache_create(size_t size, VALUE (*form)(const char *str, size_t len), bool mark, bool locking);
16
+ extern void cache_free(void *data);
17
+ extern void cache_mark(void *data);
18
+ extern void cache_set_form(struct _cache *c, VALUE (*form)(const char *str, size_t len));
19
+ extern VALUE cache_intern(struct _cache *c, const char *key, size_t len);
20
+ extern void cache_set_expunge_rate(struct _cache *c, int rate);
21
+
22
+ #endif /* CACHE_H */