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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +44 -0
- data/LICENSE +21 -0
- data/README.md +164 -0
- data/ext/oj_windows/buf.h +85 -0
- data/ext/oj_windows/cache.c +339 -0
- data/ext/oj_windows/cache.h +22 -0
- data/ext/oj_windows/cache8.c +105 -0
- data/ext/oj_windows/cache8.h +21 -0
- data/ext/oj_windows/circarray.c +64 -0
- data/ext/oj_windows/circarray.h +22 -0
- data/ext/oj_windows/code.c +214 -0
- data/ext/oj_windows/code.h +40 -0
- data/ext/oj_windows/compat.c +239 -0
- data/ext/oj_windows/custom.c +1074 -0
- data/ext/oj_windows/debug.c +126 -0
- data/ext/oj_windows/dump.c +1556 -0
- data/ext/oj_windows/dump.h +110 -0
- data/ext/oj_windows/dump_compat.c +901 -0
- data/ext/oj_windows/dump_leaf.c +162 -0
- data/ext/oj_windows/dump_object.c +710 -0
- data/ext/oj_windows/dump_strict.c +405 -0
- data/ext/oj_windows/encode.h +16 -0
- data/ext/oj_windows/err.c +57 -0
- data/ext/oj_windows/err.h +67 -0
- data/ext/oj_windows/extconf.rb +77 -0
- data/ext/oj_windows/fast.c +1710 -0
- data/ext/oj_windows/intern.c +325 -0
- data/ext/oj_windows/intern.h +22 -0
- data/ext/oj_windows/mem.c +320 -0
- data/ext/oj_windows/mem.h +53 -0
- data/ext/oj_windows/mimic_json.c +919 -0
- data/ext/oj_windows/object.c +726 -0
- data/ext/oj_windows/odd.c +245 -0
- data/ext/oj_windows/odd.h +43 -0
- data/ext/oj_windows/oj.c +2097 -0
- data/ext/oj_windows/oj.h +420 -0
- data/ext/oj_windows/parse.c +1317 -0
- data/ext/oj_windows/parse.h +113 -0
- data/ext/oj_windows/parser.c +1600 -0
- data/ext/oj_windows/parser.h +103 -0
- data/ext/oj_windows/rails.c +1484 -0
- data/ext/oj_windows/rails.h +18 -0
- data/ext/oj_windows/reader.c +222 -0
- data/ext/oj_windows/reader.h +137 -0
- data/ext/oj_windows/resolve.c +80 -0
- data/ext/oj_windows/resolve.h +12 -0
- data/ext/oj_windows/rxclass.c +144 -0
- data/ext/oj_windows/rxclass.h +26 -0
- data/ext/oj_windows/saj.c +675 -0
- data/ext/oj_windows/saj2.c +584 -0
- data/ext/oj_windows/saj2.h +23 -0
- data/ext/oj_windows/scp.c +187 -0
- data/ext/oj_windows/simd.h +47 -0
- data/ext/oj_windows/sparse.c +946 -0
- data/ext/oj_windows/stream_writer.c +329 -0
- data/ext/oj_windows/strict.c +189 -0
- data/ext/oj_windows/string_writer.c +517 -0
- data/ext/oj_windows/trace.c +72 -0
- data/ext/oj_windows/trace.h +55 -0
- data/ext/oj_windows/usual.c +1218 -0
- data/ext/oj_windows/usual.h +69 -0
- data/ext/oj_windows/util.c +136 -0
- data/ext/oj_windows/util.h +20 -0
- data/ext/oj_windows/val_stack.c +101 -0
- data/ext/oj_windows/val_stack.h +151 -0
- data/ext/oj_windows/validate.c +46 -0
- data/ext/oj_windows/wab.c +584 -0
- data/lib/oj/active_support_helper.rb +39 -0
- data/lib/oj/bag.rb +95 -0
- data/lib/oj/easy_hash.rb +52 -0
- data/lib/oj/error.rb +21 -0
- data/lib/oj/json.rb +188 -0
- data/lib/oj/mimic.rb +301 -0
- data/lib/oj/saj.rb +80 -0
- data/lib/oj/schandler.rb +143 -0
- data/lib/oj/state.rb +135 -0
- data/lib/oj/version.rb +4 -0
- data/lib/oj_windows/active_support_helper.rb +39 -0
- data/lib/oj_windows/bag.rb +95 -0
- data/lib/oj_windows/easy_hash.rb +52 -0
- data/lib/oj_windows/error.rb +21 -0
- data/lib/oj_windows/json.rb +188 -0
- data/lib/oj_windows/mimic.rb +301 -0
- data/lib/oj_windows/saj.rb +80 -0
- data/lib/oj_windows/schandler.rb +143 -0
- data/lib/oj_windows/state.rb +135 -0
- data/lib/oj_windows/version.rb +4 -0
- data/lib/oj_windows.rb +15 -0
- data/pages/Advanced.md +38 -0
- data/pages/Compatibility.md +49 -0
- data/pages/Custom.md +37 -0
- data/pages/Encoding.md +61 -0
- data/pages/InstallOptions.md +20 -0
- data/pages/JsonGem.md +60 -0
- data/pages/Modes.md +94 -0
- data/pages/Options.md +339 -0
- data/pages/Parser.md +134 -0
- data/pages/Rails.md +85 -0
- data/pages/Security.md +43 -0
- data/pages/WAB.md +12 -0
- 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
|
+
[](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 */
|