├── .github ├── FUNDING.yml └── workflows │ └── node.js.yml ├── .gitignore ├── .travis.yml ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── binding.gyp ├── index.d.ts ├── index.js ├── package.json ├── readme.md ├── src ├── node_shm.cc └── node_shm.h └── test ├── example.js ├── tsconfig.json └── typings.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ukrbublik 2 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | matrix: 20 | node-version: [14.x, 16.x, 18.x] 21 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - run: npm i --force 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | npm-debug.log 4 | package-lock.json 5 | .cache 6 | compile_commands.json 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: focal 2 | language: node_js 3 | node_js: 4 | - 14 5 | - 17 6 | install: 7 | - npm install --force 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | - george-edison55-precise-backports # cmake 13 | packages: 14 | - gcc 15 | - g++ 16 | - clang 17 | - cmake 18 | - cmake-data 19 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/local/include/node" 8 | ], 9 | "defines": [], 10 | "macFrameworkPath": [ 11 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" 12 | ], 13 | "compilerPath": "/usr/bin/clang", 14 | "cStandard": "c17", 15 | "cppStandard": "c++17", 16 | "intelliSenseMode": "macos-clang-x64" 17 | } 18 | ], 19 | "version": 4 20 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug", 11 | //"preLaunchTask": "npm: build:debug", 12 | "program": "/Users/denys.oblohin/.nvm/versions/node/v14.20.1/bin/node", 13 | "args": [ 14 | "${workspaceFolder}/test/example.js" 15 | ], 16 | "env": { 17 | "DEBUG_SHM": "1" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "__bit_reference": "cpp", 4 | "unordered_map": "cpp", 5 | "string": "cpp", 6 | "vector": "cpp" 7 | }, 8 | "terminal.integrated.defaultProfile.linux": "bash", 9 | "terminal.integrated.profiles.osx": { 10 | "zsh": { 11 | "path": "zsh", 12 | "args": [ 13 | "-i" 14 | ] 15 | } 16 | }, 17 | "terminal.integrated.defaultProfile.osx": "zsh", 18 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build:debug", 7 | "group": "build", 8 | "problemMatcher": [], 9 | "label": "Build Debug" 10 | }, 11 | { 12 | "type": "npm", 13 | "script": "build", 14 | "group": "build", 15 | "problemMatcher": [], 16 | "label": "Build Release" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Denis Oblogin 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "shm", 4 | "include_dirs": [ 5 | "src", 6 | " 3 | 4 | type Shm = (T & { key?: number }); 5 | 6 | type ShmMap = { 7 | Buffer: Shm; 8 | Int8Array: Shm; 9 | Uint8Array: Shm; 10 | Uint8ClampedArray: Shm; 11 | Int16Array: Shm; 12 | Uint16Array: Shm; 13 | Int32Array: Shm; 14 | Uint32Array: Shm; 15 | Float32Array: Shm; 16 | Float64Array: Shm; 17 | } 18 | 19 | /** 20 | * Create shared memory segment/object. 21 | * Returns null if shm already exists. 22 | */ 23 | export function create(count: number, typeKey?: K, key?: number | string, perm?: string): ShmMap[K] | null; 24 | 25 | /** 26 | * Get shared memory segment/object. 27 | * Returns null if shm not exists. 28 | */ 29 | export function get(key: number | string, typeKey?: K): ShmMap[K] | null; 30 | 31 | /** 32 | * Detach shared memory segment/object. 33 | * For System V: If there are no other attaches for this segment, it will be destroyed. 34 | * Returns 0 on destroy, 1 on detach, -1 on error 35 | */ 36 | export function detach(key: number | string, forceDestoy?: boolean): number; 37 | 38 | /** 39 | * Destroy shared memory segment/object. 40 | */ 41 | export function destroy(key: number | string): boolean; 42 | 43 | /** 44 | * Detach all created and getted shared memory segments/objects. 45 | * Will be automatically called on process exit/termination. 46 | */ 47 | export function detachAll(): number; 48 | 49 | /** 50 | * Get total size of all *used* shared memory in bytes. 51 | */ 52 | export function getTotalSize(): number; 53 | 54 | /** 55 | * Get total size of all *created* shared memory in bytes. 56 | */ 57 | export function getTotalCreatedSize(): number; 58 | 59 | /** 60 | * Max length of shared memory segment (count of elements, not bytes). 61 | * 2^31 for 64bit, 2^30 for 32bit. 62 | */ 63 | export const LengthMax: number; 64 | 65 | /** 66 | * Types of shared memory object 67 | */ 68 | export const BufferType: { 69 | [key: string]: number; 70 | } 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const buildDir = process.env.DEBUG_SHM == 1 ? 'Debug' : 'Release'; 3 | const shm = require('./build/' + buildDir + '/shm.node'); 4 | 5 | const uint32Max = Math.pow(2,32) - 1; 6 | const keyMin = 1; 7 | const keyMax = uint32Max - keyMin; 8 | const lengthMin = 1; 9 | /** 10 | * Max length of shared memory segment (count of elements, not bytes) 11 | */ 12 | const lengthMax = shm.NODE_BUFFER_MAX_LENGTH; 13 | 14 | const cleanup = function () { 15 | try { 16 | var cnt = shm.detachAll(); 17 | if (cnt > 0) 18 | console.info('shm segments destroyed:', cnt); 19 | } catch(exc) { console.error(exc); } 20 | }; 21 | process.on('exit', cleanup); 22 | 23 | /** 24 | * Types of shared memory object 25 | */ 26 | const BufferType = { 27 | 'Buffer': shm.SHMBT_BUFFER, 28 | 'Int8Array': shm.SHMBT_INT8, 29 | 'Uint8Array': shm.SHMBT_UINT8, 30 | 'Uint8ClampedArray': shm.SHMBT_UINT8CLAMPED, 31 | 'Int16Array': shm.SHMBT_INT16, 32 | 'Uint16Array': shm.SHMBT_UINT16, 33 | 'Int32Array': shm.SHMBT_INT32, 34 | 'Uint32Array': shm.SHMBT_UINT32, 35 | 'Float32Array': shm.SHMBT_FLOAT32, 36 | 'Float64Array': shm.SHMBT_FLOAT64, 37 | }; 38 | const BufferTypeSizeof = { 39 | 'Buffer': 1, 40 | 'Int8Array': 1, 41 | 'Uint8Array': 1, 42 | 'Uint8ClampedArray': 1, 43 | 'Int16Array': 2, 44 | 'Uint16Array': 2, 45 | 'Int32Array': 4, 46 | 'Uint32Array': 4, 47 | 'Float32Array': 4, 48 | 'Float64Array': 8, 49 | }; 50 | 51 | /** 52 | * Create System V or POSIX shared memory 53 | * @param {int} count - number of elements 54 | * @param {string} typeKey - see keys of BufferType 55 | * @param {int/string/null} key - integer key for System V shared memory segment, or null to autogenerate, 56 | * or string name for POSIX shared memory object, should start with '/'. 57 | * @param {string} permStr - permissions, default is 660 58 | * @return {mixed/null} shared memory buffer/array object, or null if already exists with provided key 59 | * Class depends on param typeKey: Buffer or descendant of TypedArray. 60 | * For System V: returned object has property 'key' - integer key of created shared memory segment 61 | */ 62 | function create(count, typeKey /*= 'Buffer'*/, key /*= null*/, permStr /*= '660'*/) { 63 | if (typeof key === 'string') { 64 | return createPosix(key, count, typeKey, permStr); 65 | } 66 | 67 | if (typeKey === undefined) 68 | typeKey = 'Buffer'; 69 | if (key === undefined) 70 | key = null; 71 | if (BufferType[typeKey] === undefined) 72 | throw new Error("Unknown type key " + typeKey); 73 | if (key !== null) { 74 | if (!(Number.isSafeInteger(key) && key >= keyMin && key <= keyMax)) 75 | throw new RangeError('Shm key should be ' + keyMin + ' .. ' + keyMax); 76 | } 77 | if (permStr === undefined || isNaN( Number.parseInt(permStr, 8))) 78 | permStr = '660'; 79 | const perm = Number.parseInt(permStr, 8); 80 | 81 | var type = BufferType[typeKey]; 82 | //var size1 = BufferTypeSizeof[typeKey]; 83 | //var size = size1 * count; 84 | if (!(Number.isSafeInteger(count) && count >= lengthMin && count <= lengthMax)) 85 | throw new RangeError('Count should be ' + lengthMin + ' .. ' + lengthMax); 86 | let res; 87 | if (key) { 88 | res = shm.get(key, count, shm.IPC_CREAT|shm.IPC_EXCL|perm, 0, type); 89 | } else { 90 | do { 91 | key = _keyGen(); 92 | res = shm.get(key, count, shm.IPC_CREAT|shm.IPC_EXCL|perm, 0, type); 93 | } while(!res); 94 | } 95 | if (res) { 96 | res.key = key; 97 | } 98 | return res; 99 | } 100 | 101 | /** 102 | * Create POSIX shared memory object 103 | * @param {string} name - string name of shared memory object, should start with '/' 104 | * Eg. '/test' will create virtual file '/dev/shm/test' in tmpfs for Linix 105 | * @param {int} count - number of elements 106 | * @param {string} typeKey - see keys of BufferType 107 | * @param {string} permStr - permissions, default is 660 108 | * @return {mixed/null} shared memory buffer/array object, or null if already exists with provided name 109 | * Class depends on param typeKey: Buffer or descendant of TypedArray 110 | */ 111 | function createPosix(name, count, typeKey /*= 'Buffer'*/, permStr /*= '660'*/) { 112 | if (typeKey === undefined) 113 | typeKey = 'Buffer'; 114 | if (BufferType[typeKey] === undefined) 115 | throw new Error("Unknown type key " + typeKey); 116 | if (permStr === undefined || isNaN( Number.parseInt(permStr, 8))) 117 | permStr = '660'; 118 | const perm = Number.parseInt(permStr, 8); 119 | 120 | const type = BufferType[typeKey]; 121 | //var size1 = BufferTypeSizeof[typeKey]; 122 | //var size = size1 * count; 123 | if (!(Number.isSafeInteger(count) && count >= lengthMin && count <= lengthMax)) 124 | throw new RangeError('Count should be ' + lengthMin + ' .. ' + lengthMax); 125 | const oflag = shm.O_CREAT | shm.O_RDWR | shm.O_EXCL; 126 | const mmap_flags = shm.MAP_SHARED; 127 | const res = shm.getPosix(name, count, oflag, perm, mmap_flags, type); 128 | 129 | return res; 130 | } 131 | 132 | /** 133 | * Get System V/POSIX shared memory 134 | * @param {int/string} key - integer key of System V shared memory segment, or string name of POSIX shared memory object 135 | * @param {string} typeKey - see keys of BufferType 136 | * @return {mixed/null} shared memory buffer/array object, see create(), or null if not exists 137 | */ 138 | function get(key, typeKey /*= 'Buffer'*/) { 139 | if (typeof key === 'string') { 140 | return getPosix(key, typeKey); 141 | } 142 | if (typeKey === undefined) 143 | typeKey = 'Buffer'; 144 | if (BufferType[typeKey] === undefined) 145 | throw new Error("Unknown type key " + typeKey); 146 | var type = BufferType[typeKey]; 147 | if (!(Number.isSafeInteger(key) && key >= keyMin && key <= keyMax)) 148 | throw new RangeError('Shm key should be ' + keyMin + ' .. ' + keyMax); 149 | let res = shm.get(key, 0, 0, 0, type); 150 | if (res) { 151 | res.key = key; 152 | } 153 | return res; 154 | } 155 | 156 | /** 157 | * Get POSIX shared memory object 158 | * @param {string} name - string name of shared memory object 159 | * @param {string} typeKey - see keys of BufferType 160 | * @return {mixed/null} shared memory buffer/array object, see createPosix(), or null if not exists 161 | */ 162 | function getPosix(name, typeKey /*= 'Buffer'*/) { 163 | if (typeKey === undefined) 164 | typeKey = 'Buffer'; 165 | if (BufferType[typeKey] === undefined) 166 | throw new Error("Unknown type key " + typeKey); 167 | var type = BufferType[typeKey]; 168 | const oflag = shm.O_RDWR; 169 | const mmap_flags = shm.MAP_SHARED; 170 | let res = shm.getPosix(name, 0, oflag, 0, mmap_flags, type); 171 | return res; 172 | } 173 | 174 | /** 175 | * Detach System V/POSIX shared memory 176 | * For System V: If there are no other attaches for this segment, it will be destroyed 177 | * For POSIX: It will be destroyed only if `forceDestroy` is true 178 | * @param {int/string} key - integer key of System V shared memory segment, or string name of POSIX shared memory object 179 | * @param {bool} forceDestroy - true to destroy even there are other attaches 180 | * @return {int} 0 on destroy, or count of left attaches, or -1 if not exists 181 | */ 182 | function detach(key, forceDestroy /*= false*/) { 183 | if (typeof key === 'string') { 184 | return detachPosix(key, forceDestroy); 185 | } 186 | if (forceDestroy === undefined) 187 | forceDestroy = false; 188 | return shm.detach(key, forceDestroy); 189 | } 190 | 191 | /** 192 | * Detach POSIX shared memory object 193 | * @param {string} name - string name of shared memory object 194 | * @param {bool} forceDestroy - true to unlink 195 | * @return {int} 0 on destroy, 1 on detach, -1 if not exists 196 | */ 197 | function detachPosix(name, forceDestroy /*= false*/) { 198 | if (forceDestroy === undefined) 199 | forceDestroy = false; 200 | return shm.detachPosix(name, forceDestroy); 201 | } 202 | 203 | /** 204 | * Destroy System V/POSIX shared memory 205 | * @param {int/string} key - integer key of System V shared memory segment, or string name of POSIX shared memory object 206 | * @return {boolean} 207 | */ 208 | function destroy(key) { 209 | return detach(key, true) == 0; 210 | } 211 | 212 | /** 213 | * Detach all created and getted shared memory objects (both System V and POSIX) 214 | * Will be automatically called on process exit/termination 215 | * @return {int} count of destroyed System V segments 216 | */ 217 | function detachAll() { 218 | return shm.detachAll(); 219 | } 220 | 221 | function _keyGen() { 222 | return keyMin + Math.floor(Math.random() * keyMax); 223 | } 224 | 225 | //Exports 226 | module.exports.create = create; 227 | module.exports.createPosix = createPosix; 228 | module.exports.get = get; 229 | module.exports.getPosix = getPosix; 230 | module.exports.detach = detach; 231 | module.exports.detachPosix = detachPosix; 232 | module.exports.destroy = destroy; 233 | module.exports.detachAll = detachAll; 234 | module.exports.getTotalSize = shm.getTotalUsedSize; 235 | module.exports.getTotalCreatedSize = shm.getTotalAllocatedSize; 236 | module.exports.BufferType = BufferType; 237 | module.exports.LengthMax = lengthMax; 238 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shm-typed-array", 3 | "version": "0.1.1", 4 | "description": "IPC shared memory for NodeJs. Use as Buffer or TypedArray.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/ukrbublik/shm-typed-array.git" 10 | }, 11 | "keywords": [ 12 | "ipc", 13 | "shm", 14 | "systemv", 15 | "system-v", 16 | "POSIX", 17 | "shared memory", 18 | "typed array", 19 | "TypedArray", 20 | "ArrayBuffer", 21 | "Buffer", 22 | "Int8Array", 23 | "Uint8Array", 24 | "Uint8ClampedArray", 25 | "Int16Array", 26 | "Uint16Array", 27 | "Int32Array", 28 | "Uint32Array", 29 | "Float32Array", 30 | "Float64Array" 31 | ], 32 | "author": { 33 | "name": "Oblogin Denis", 34 | "email": "ukrbublik@gmail.com", 35 | "url": "https://github.com/ukrbublik" 36 | }, 37 | "license": "MIT", 38 | "dependencies": { 39 | "nan": "^2.18.0" 40 | }, 41 | "os": [ 42 | "!win32" 43 | ], 44 | "engines": { 45 | "node": ">=4.0.0" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/ukrbublik/shm-typed-array/issues" 49 | }, 50 | "homepage": "https://github.com/ukrbublik/shm-typed-array", 51 | "scripts": { 52 | "build": "node-gyp configure && node-gyp rebuild", 53 | "build:debug": "node-gyp configure --debug && node-gyp rebuild --debug", 54 | "install": "npm run build", 55 | "test": "node test/example.js", 56 | "test:types": "typings-tester --config test/tsconfig.json test/typings.ts" 57 | }, 58 | "devDependencies": { 59 | "@types/node": "^20.2.3", 60 | "typescript": "^4.8.4", 61 | "typings-tester": "^0.3.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | IPC shared memory for Node.js 2 | Use as `Buffer` or `TypedArray` 3 | Supports System V and POSIX shared memory 4 | [![npm](https://img.shields.io/npm/v/shm-typed-array.svg)](https://www.npmjs.com/package/shm-typed-array) [![travis](https://travis-ci.org/ukrbublik/shm-typed-array.svg?branch=master)](https://travis-ci.com/github/ukrbublik/shm-typed-array) 5 | 6 | 7 | # Install 8 | ``` bash 9 | $ npm install shm-typed-array 10 | ``` 11 | Windows is not supported. 12 | 13 | # System V vs POSIX 14 | 15 | Versions 0.0.* support only [System V memory segments](https://man7.org/linux/man-pages/man7/sysvipc.7.html). 16 | Starting from version 0.1.0 [POSIX memory objects](https://man7.org/linux/man-pages/man7/shm_overview.7.html) are also supported. 17 | 18 | System V is the classic way to use shared memory, stores IPC objects internally in the kernel. 19 | POSIX is newer, but not fully supported in MacOS, uses the file system interface. 20 | 21 | To create POSIX memory objects, use string parameter `key` in [API](#api). 22 | Eg. `shm.create(100, 'Buffer', '/test')` will create virtual file `/dev/shm/test` in tmpfs for Linix. 23 | 24 | To create System V memory segment, use numeric parameter `key` in [API](#api). 25 | Eg. `shm.create(100, 'Buffer', 1234)` or `shm.create(100)` to autogenerate key. 26 | 27 | 28 | # API 29 | 30 | ### shm.create (count, typeKey, key?, perm?) 31 | Create shared memory segment/object. 32 | `count` - number of elements (not bytes), 33 | `typeKey` - type of elements (`'Buffer'` by default, see list below), 34 | `key` - integer/null to create System V memory segment, or string to create POSIX memory object, 35 | `perm` - permissions flag (default is `660`). 36 | Returns shared memory `Buffer` or descendant of `TypedArray` object, class depends on param `typeKey`. 37 | Or returns `null` if shm already exists with provided key. 38 | *For System V:* returned object has property `key` - integer key of created System V shared memory segment, to use in `shm.get(key)`. 39 | *For POSIX:* shared memory objects are not automatically destroyed. You should call `shm.destroy(key)` manually on process cleanup or if you don't need the object anymore. 40 | 41 | ### shm.get (key, typeKey) 42 | Get created shared memory segment/object by key. 43 | Returns `null` if shm not exists with provided key. 44 | 45 | ### shm.detach (key, forceDestroy?) 46 | Detach shared memory segment/object. 47 | *For System V:* If there are no other attaches for a segment, it will be destroyed automatically (even if `forceDestroy` is not true). 48 | *For POSIX:* Unlike System V segments, POSIX object will not be destroyed automatically. You need to destroy it manually by providing true to `forceDestroy` argument or using `shm.destroy(key)`. 49 | 50 | ### shm.destroy (key) 51 | Destroy shared memory segment/object. 52 | Same as `shm.detach(key, true)` 53 | 54 | ### shm.detachAll () 55 | Detach all created shared memory segments and objects. 56 | Will be automatically called on process exit, see [Cleanup](#cleanup). 57 | 58 | ### shm.getTotalSize() 59 | Get total size of all *used* (mapped) shared memory in bytes. 60 | 61 | ### shm.getTotalCreatedSize() 62 | Get total size of all *created* shared memory in bytes. 63 | 64 | ### shm.LengthMax 65 | Max length of shared memory segment (count of elements, not bytes) 66 | 2^31 for 64bit, 2^30 for 32bit 67 | 68 | #### Types: 69 | ```js 70 | shm.BufferType = { 71 | 'Buffer': shm.SHMBT_BUFFER, 72 | 'Int8Array': shm.SHMBT_INT8, 73 | 'Uint8Array': shm.SHMBT_UINT8, 74 | 'Uint8ClampedArray': shm.SHMBT_UINT8CLAMPED, 75 | 'Int16Array': shm.SHMBT_INT16, 76 | 'Uint16Array': shm.SHMBT_UINT16, 77 | 'Int32Array': shm.SHMBT_INT32, 78 | 'Uint32Array': shm.SHMBT_UINT32, 79 | 'Float32Array': shm.SHMBT_FLOAT32, 80 | 'Float64Array': shm.SHMBT_FLOAT64, 81 | }; 82 | ``` 83 | 84 | 85 | # Cleanup 86 | This library does cleanup of created SHM segments/objects only on normal exit of process, see [`exit` event](https://nodejs.org/api/process.html#process_event_exit). 87 | If you want to do cleanup on terminate signals like `SIGINT`, `SIGTERM`, please use [node-cleanup](https://github.com/jtlapp/node-cleanup) / [node-death](https://github.com/jprichardson/node-death) and add code to exit handlers: 88 | ```js 89 | shm.detachAll(); 90 | ``` 91 | 92 | Also note that POSIX shared memory objects are not automatically destroyed. 93 | You should call `shm.destroy('/your_name')` manually if you don't need it anymore. 94 | 95 | 96 | # Usage 97 | See [example.js](https://github.com/ukrbublik/shm-typed-array/blob/master/test/example.js) 98 | 99 | 100 | Usage of memory segments: 101 | ``` js 102 | const cluster = require('cluster'); 103 | const shm = require('shm-typed-array'); 104 | 105 | var buf, arr; 106 | if (cluster.isMaster) { 107 | buf = shm.create(4096); //4KB 108 | arr = shm.create(1000000*100, 'Float32Array'); //100M floats 109 | buf[0] = 1; 110 | arr[0] = 10.0; 111 | console.log('[Master] Typeof buf:', buf.constructor.name, 112 | 'Typeof arr:', arr.constructor.name); 113 | 114 | var worker = cluster.fork(); 115 | worker.on('online', function() { 116 | this.send({ msg: 'shm', bufKey: buf.key, arrKey: arr.key }); 117 | var i = 0; 118 | setInterval(function() { 119 | buf[0] += 1; 120 | arr[0] /= 2; 121 | console.log(i + ' [Master] Set buf[0]=', buf[0], 122 | ' arr[0]=', arr ? arr[0] : null); 123 | i++; 124 | if (i == 5) { 125 | groupSuicide(); 126 | } 127 | }, 500); 128 | }); 129 | } else { 130 | process.on('message', function(data) { 131 | var msg = data.msg; 132 | if (msg == 'shm') { 133 | buf = shm.get(data.bufKey); 134 | arr = shm.get(data.arrKey, 'Float32Array'); 135 | console.log('[Worker] Typeof buf:', buf.constructor.name, 136 | 'Typeof arr:', arr.constructor.name); 137 | var i = 0; 138 | setInterval(function() { 139 | console.log(i + ' [Worker] Get buf[0]=', buf[0], 140 | ' arr[0]=', arr ? arr[0] : null); 141 | i++; 142 | if (i == 2) { 143 | shm.detach(data.arrKey); 144 | arr = null; //otherwise process will drop 145 | } 146 | }, 500); 147 | } else if (msg == 'exit') { 148 | process.exit(); 149 | } 150 | }); 151 | } 152 | 153 | function groupSuicide() { 154 | if (cluster.isMaster) { 155 | for (var id in cluster.workers) { 156 | cluster.workers[id].send({ msg: 'exit'}); 157 | cluster.workers[id].destroy(); 158 | } 159 | process.exit(); 160 | } 161 | } 162 | ``` 163 | 164 | **Output:** 165 | ``` 166 | [Master] Typeof buf: Buffer Typeof arr: Float32Array 167 | [Worker] Typeof buf: Buffer Typeof arr: Float32Array 168 | 0 [Master] Set buf[0]= 2 arr[0]= 5 169 | 0 [Worker] Get buf[0]= 2 arr[0]= 5 170 | 1 [Master] Set buf[0]= 3 arr[0]= 2.5 171 | 1 [Worker] Get buf[0]= 3 arr[0]= 2.5 172 | 2 [Master] Set buf[0]= 4 arr[0]= 1.25 173 | 2 [Worker] Get buf[0]= 4 arr[0]= null 174 | 3 [Master] Set buf[0]= 5 arr[0]= 0.625 175 | 3 [Worker] Get buf[0]= 5 arr[0]= null 176 | 4 [Master] Set buf[0]= 6 arr[0]= 0.3125 177 | shm segments destroyed: 2 178 | ``` 179 | -------------------------------------------------------------------------------- /src/node_shm.cc: -------------------------------------------------------------------------------- 1 | #include "node_shm.h" 2 | #include "node.h" 3 | 4 | //------------------------------- 5 | 6 | #if NODE_MODULE_VERSION > NODE_16_0_MODULE_VERSION 7 | namespace { 8 | void emptyBackingStoreDeleter(void*, size_t, void*) {} 9 | } 10 | #endif 11 | 12 | namespace node { 13 | namespace Buffer { 14 | 15 | using v8::ArrayBuffer; 16 | using v8::ArrayBufferCreationMode; 17 | using v8::EscapableHandleScope; 18 | using v8::Isolate; 19 | using v8::Local; 20 | using v8::MaybeLocal; 21 | using v8::Object; 22 | using v8::Integer; 23 | using v8::Maybe; 24 | using v8::String; 25 | using v8::Value; 26 | using v8::Int8Array; 27 | using v8::Uint8Array; 28 | using v8::Uint8ClampedArray; 29 | using v8::Int16Array; 30 | using v8::Uint16Array; 31 | using v8::Int32Array; 32 | using v8::Uint32Array; 33 | using v8::Float32Array; 34 | using v8::Float64Array; 35 | 36 | 37 | MaybeLocal NewTyped( 38 | Isolate* isolate, 39 | char* data, 40 | size_t count 41 | #if NODE_MODULE_VERSION > IOJS_2_0_MODULE_VERSION 42 | , node::Buffer::FreeCallback callback 43 | #else 44 | , node::smalloc::FreeCallback callback 45 | #endif 46 | , void *hint 47 | , ShmBufferType type 48 | ) { 49 | size_t length = count * getSizeForShmBufferType(type); 50 | 51 | EscapableHandleScope scope(isolate); 52 | 53 | /* 54 | MaybeLocal mlarr = node::Buffer::New( 55 | isolate, data, length, callback, hint); 56 | Local larr = mlarr.ToLocalChecked(); 57 | Uint8Array* arr = (Uint8Array*) *larr; 58 | Local ab = arr->Buffer(); 59 | */ 60 | 61 | #if NODE_MODULE_VERSION > NODE_16_0_MODULE_VERSION 62 | Local ab = ArrayBuffer::New(isolate, 63 | ArrayBuffer::NewBackingStore(data, length, &emptyBackingStoreDeleter, nullptr)); 64 | #else 65 | Local ab = ArrayBuffer::New(isolate, data, length, 66 | ArrayBufferCreationMode::kExternalized); 67 | #endif 68 | 69 | Local ui; 70 | switch(type) { 71 | case SHMBT_INT8: 72 | ui = Int8Array::New(ab, 0, count); 73 | break; 74 | case SHMBT_UINT8: 75 | ui = Uint8Array::New(ab, 0, count); 76 | break; 77 | case SHMBT_UINT8CLAMPED: 78 | ui = Uint8ClampedArray::New(ab, 0, count); 79 | break; 80 | case SHMBT_INT16: 81 | ui = Int16Array::New(ab, 0, count); 82 | break; 83 | case SHMBT_UINT16: 84 | ui = Uint16Array::New(ab, 0, count); 85 | break; 86 | case SHMBT_INT32: 87 | ui = Int32Array::New(ab, 0, count); 88 | break; 89 | case SHMBT_UINT32: 90 | ui = Uint32Array::New(ab, 0, count); 91 | break; 92 | case SHMBT_FLOAT32: 93 | ui = Float32Array::New(ab, 0, count); 94 | break; 95 | default: 96 | case SHMBT_FLOAT64: 97 | ui = Float64Array::New(ab, 0, count); 98 | break; 99 | } 100 | 101 | return scope.Escape(ui); 102 | } 103 | 104 | } 105 | } 106 | 107 | //------------------------------- 108 | 109 | namespace Nan { 110 | 111 | inline MaybeLocal NewTypedBuffer( 112 | char *data 113 | , size_t count 114 | #if NODE_MODULE_VERSION > IOJS_2_0_MODULE_VERSION 115 | , node::Buffer::FreeCallback callback 116 | #else 117 | , node::smalloc::FreeCallback callback 118 | #endif 119 | , void *hint 120 | , ShmBufferType type 121 | ) { 122 | size_t length = count * getSizeForShmBufferType(type); 123 | 124 | if (type != SHMBT_BUFFER) { 125 | assert(count <= node::Buffer::kMaxLength && "too large typed buffer"); 126 | #if NODE_MODULE_VERSION > IOJS_2_0_MODULE_VERSION 127 | return node::Buffer::NewTyped( 128 | Isolate::GetCurrent(), data, count, callback, hint, type); 129 | #else 130 | return MaybeLocal(node::Buffer::NewTyped( 131 | Isolate::GetCurrent(), data, count, callback, hint, type)); 132 | #endif 133 | } else { 134 | assert(length <= node::Buffer::kMaxLength && "too large buffer"); 135 | #if NODE_MODULE_VERSION > IOJS_2_0_MODULE_VERSION 136 | return node::Buffer::New( 137 | Isolate::GetCurrent(), data, length, callback, hint); 138 | #else 139 | return MaybeLocal(node::Buffer::New( 140 | Isolate::GetCurrent(), data, length, callback, hint)); 141 | #endif 142 | } 143 | 144 | } 145 | 146 | } 147 | 148 | //------------------------------- 149 | 150 | namespace node { 151 | namespace node_shm { 152 | 153 | using node::AtExit; 154 | using v8::Local; 155 | using v8::Number; 156 | using v8::Object; 157 | using v8::String; 158 | using v8::Value; 159 | 160 | enum ShmType { 161 | SHM_DELETED = -1, 162 | SHM_TYPE_SYSTEMV = 0, 163 | SHM_TYPE_POSIX = 1, 164 | }; 165 | 166 | struct ShmMeta { 167 | ShmType type; 168 | int id; 169 | void* memAddr; 170 | size_t memSize; 171 | std::string name; 172 | bool isOwner; 173 | }; 174 | 175 | #define NOT_FOUND_IND ULONG_MAX 176 | #define NO_SHMID INT_MIN 177 | 178 | // Array to keep info about created segments, call it "meta array" 179 | std::vector shmMeta; 180 | size_t shmAllocatedBytes = 0; 181 | size_t shmMappedBytes = 0; 182 | 183 | // Declare private methods 184 | static int detachAllShm(); 185 | static int detachShmSegmentOrObject(ShmMeta& meta, bool force = false, bool onExit = false); 186 | static int detachShmSegment(ShmMeta& meta, bool force = false, bool onExit = false); 187 | static int detachPosixShmObject(ShmMeta& meta, bool force = false, bool onExit = false); 188 | static size_t addShmSegmentInfo(ShmMeta& meta); 189 | static bool removeShmSegmentInfo(size_t ind); 190 | 191 | static void FreeCallback(char* data, void* hint); 192 | #if NODE_MODULE_VERSION < NODE_16_0_MODULE_VERSION 193 | static void Init(Local target); 194 | #else 195 | static void Init(Local target, Local module, void* priv); 196 | #endif 197 | static void AtNodeExit(void*); 198 | 199 | 200 | // Detach all System V segments and POSIX objects (don't force destroy) 201 | // Returns count of destroyed System V segments 202 | static int detachAllShm() { 203 | int res = 0; 204 | if (shmMeta.size() > 0) { 205 | for (std::vector::iterator it = shmMeta.begin(); it != shmMeta.end(); ++it) { 206 | if (detachShmSegmentOrObject(*it, false, true) == 0) 207 | res++; 208 | } 209 | } 210 | return res; 211 | } 212 | 213 | // Add meta to array 214 | static size_t addShmSegmentInfo(ShmMeta& meta) { 215 | shmMeta.push_back(meta); 216 | size_t ind = shmMeta.size() - 1; 217 | return ind; 218 | } 219 | 220 | // Find in mera array 221 | static size_t findShmSegmentInfo(ShmMeta& search) { 222 | const auto found = std::find_if(shmMeta.begin(), shmMeta.end(), 223 | [&](const auto& el) { 224 | return el.type == search.type 225 | && (search.id == NO_SHMID || el.id == search.id) 226 | && (search.name.length() == 0 || search.name.compare(el.name) == 0); 227 | } 228 | ); 229 | size_t ind = found != shmMeta.end() ? std::distance(shmMeta.begin(), found) : NOT_FOUND_IND; 230 | return ind; 231 | } 232 | 233 | // Remove from meta array 234 | static bool removeShmSegmentInfo(size_t ind) { 235 | // TODO: 236 | // Remove meta data from vector with .erase() 237 | // But this requires to have key pairs `meta id` <-> `index in vector` 238 | // And pass `meta id` to `hint` in `NewTypedBuffer()` (for `FreeCallback`) 239 | return false; 240 | } 241 | 242 | // Detach System V segment or POSIX object 243 | // Returns 0 if destroyed, > 0 if detached, -1 if not exists 244 | static int detachShmSegmentOrObject(ShmMeta& meta, bool force, bool onExit) { 245 | if (meta.type == SHM_TYPE_SYSTEMV) { 246 | return detachShmSegment(meta, force, onExit); 247 | } else if (meta.type == SHM_TYPE_POSIX) { 248 | return detachPosixShmObject(meta, force, onExit); 249 | } 250 | return -1; 251 | } 252 | 253 | // Detach System V segment 254 | // Returns 0 if destroyed, or count of left attaches, or -1 if not exists 255 | static int detachShmSegment(ShmMeta& meta, bool force, bool onExit) { 256 | int err; 257 | struct shmid_ds shminf; 258 | //detach 259 | bool attached = meta.memAddr != NULL; 260 | err = attached ? shmdt(meta.memAddr) : 0; 261 | if (err == 0) { 262 | if (attached) { 263 | shmMappedBytes -= meta.memSize; 264 | } 265 | meta.memAddr = NULL; 266 | if (meta.id == NO_SHMID) { 267 | // meta is obsolete, should be deleted from meta array 268 | return 0; 269 | } 270 | //get stat 271 | err = shmctl(meta.id, IPC_STAT, &shminf); 272 | if (err == 0) { 273 | //destroy if there are no more attaches or force==true 274 | if (force || shminf.shm_nattch == 0) { 275 | err = shmctl(meta.id, IPC_RMID, 0); 276 | if (err == 0) { 277 | shmAllocatedBytes -= meta.memSize; // shminf.shminf.shm_segsz 278 | meta.memSize = 0; 279 | meta.id = 0; 280 | meta.type = SHM_DELETED; 281 | return 0; //detached and destroyed 282 | } else { 283 | if (!onExit) 284 | Nan::ThrowError(strerror(errno)); 285 | } 286 | } else { 287 | return shminf.shm_nattch; //detached, but not destroyed 288 | } 289 | } else { 290 | switch(errno) { 291 | case EIDRM: // deleted shmid 292 | case EINVAL: // not valid shmid 293 | return -1; 294 | break; 295 | default: 296 | if (!onExit) 297 | Nan::ThrowError(strerror(errno)); 298 | break; 299 | } 300 | 301 | if (!onExit) 302 | Nan::ThrowError(strerror(errno)); 303 | } 304 | } else { 305 | switch(errno) { 306 | case EINVAL: // wrong addr 307 | default: 308 | if (!onExit) 309 | Nan::ThrowError(strerror(errno)); 310 | break; 311 | } 312 | } 313 | return -1; 314 | } 315 | 316 | // Detach POSIX object 317 | // Returns 0 if deleted, 1 if detached, -1 if not exists 318 | static int detachPosixShmObject(ShmMeta& meta, bool force, bool onExit) { 319 | int err; 320 | //detach 321 | bool attached = meta.memAddr != NULL; 322 | err = attached ? munmap(meta.memAddr, meta.memSize) : 0; 323 | if (err == 0) { 324 | if (attached) { 325 | shmMappedBytes -= meta.memSize; 326 | } 327 | meta.memAddr = NULL; 328 | if (meta.name.empty()) { 329 | // meta is obsolete, should be deleted from meta array 330 | return 0; 331 | } 332 | //unlink 333 | if (force) { 334 | err = shm_unlink(meta.name.c_str()); 335 | if (err == 0) { 336 | shmAllocatedBytes -= meta.memSize; 337 | meta.memSize = 0; 338 | meta.name.clear(); 339 | meta.type = SHM_DELETED; 340 | return 0; //detached and destroyed 341 | } else { 342 | switch(errno) { 343 | case ENOENT: // not exists 344 | return -1; 345 | break; 346 | default: 347 | if (!onExit) 348 | Nan::ThrowError(strerror(errno)); 349 | break; 350 | } 351 | } 352 | } else { 353 | return 1; //detached, but not destroyed 354 | } 355 | } else { 356 | switch(errno) { 357 | case EINVAL: // wrong addr 358 | default: 359 | if (!onExit) 360 | Nan::ThrowError(strerror(errno)); 361 | break; 362 | } 363 | } 364 | return -1; 365 | } 366 | 367 | // Used only when creating byte-array (Buffer), not typed array 368 | // Because impl of CallbackInfo::New() is not public (see https://github.com/nodejs/node/blob/v6.x/src/node_buffer.cc) 369 | // Developer can detach shared memory segments manually by shm.detach() 370 | // Also shm.detachAll() will be called on process termination 371 | static void FreeCallback(char* data, void* hint) { 372 | size_t metaInd = reinterpret_cast(hint); 373 | ShmMeta meta = shmMeta[metaInd]; 374 | //void* addr = (void*) data; 375 | //assert(meta->memAddr == addr); 376 | 377 | detachShmSegmentOrObject(meta, false, true); 378 | removeShmSegmentInfo(metaInd); 379 | } 380 | 381 | NAN_METHOD(get) { 382 | Nan::HandleScope scope; 383 | int err; 384 | struct shmid_ds shminf; 385 | key_t key = Nan::To(info[0]).FromJust(); 386 | size_t count = Nan::To(info[1]).FromJust(); 387 | int shmflg = Nan::To(info[2]).FromJust(); 388 | int at_shmflg = Nan::To(info[3]).FromJust(); 389 | ShmBufferType type = (ShmBufferType) Nan::To(info[4]).FromJust(); 390 | size_t size = count * getSizeForShmBufferType(type); 391 | bool isCreate = (size > 0); 392 | 393 | int shmid = shmget(key, size, shmflg); 394 | if (shmid == -1) { 395 | switch(errno) { 396 | case EEXIST: // already exists 397 | case EIDRM: // scheduled for deletion 398 | case ENOENT: // not exists 399 | info.GetReturnValue().SetNull(); 400 | return; 401 | case EINVAL: // should be SHMMIN <= size <= SHMMAX 402 | return Nan::ThrowRangeError(strerror(errno)); 403 | default: 404 | return Nan::ThrowError(strerror(errno)); 405 | } 406 | } else { 407 | if (!isCreate) { 408 | err = shmctl(shmid, IPC_STAT, &shminf); 409 | if (err == 0) { 410 | size = shminf.shm_segsz; 411 | count = size / getSizeForShmBufferType(type); 412 | } else { 413 | return Nan::ThrowError(strerror(errno)); 414 | } 415 | } 416 | 417 | void* res = shmat(shmid, NULL, at_shmflg); 418 | if (res == (void *)-1) { 419 | return Nan::ThrowError(strerror(errno)); 420 | } 421 | 422 | ShmMeta meta = { 423 | .type=SHM_TYPE_SYSTEMV, .id=shmid, .memAddr=res, .memSize=size, .name="", .isOwner=isCreate 424 | }; 425 | size_t metaInd = findShmSegmentInfo(meta); 426 | if (metaInd == NOT_FOUND_IND) { 427 | metaInd = addShmSegmentInfo(meta); 428 | } 429 | if (isCreate) { 430 | shmAllocatedBytes += size; 431 | shmMappedBytes += size; 432 | } else { 433 | shmMappedBytes += size; 434 | } 435 | 436 | info.GetReturnValue().Set(Nan::NewTypedBuffer( 437 | reinterpret_cast(res), 438 | count, 439 | FreeCallback, 440 | reinterpret_cast(static_cast(metaInd)), 441 | type 442 | ).ToLocalChecked()); 443 | } 444 | } 445 | 446 | NAN_METHOD(getPosix) { 447 | Nan::HandleScope scope; 448 | if (!info[0]->IsString()) { 449 | return Nan::ThrowTypeError("Argument name must be a string"); 450 | } 451 | std::string name = (*Nan::Utf8String(info[0])); 452 | size_t count = Nan::To(info[1]).FromJust(); 453 | int oflag = Nan::To(info[2]).FromJust(); 454 | mode_t mode = Nan::To(info[3]).FromJust(); 455 | int mmap_flags = Nan::To(info[4]).FromJust(); 456 | ShmBufferType type = (ShmBufferType) Nan::To(info[5]).FromJust(); 457 | size_t size = count * getSizeForShmBufferType(type); 458 | bool isCreate = (size > 0); 459 | size_t realSize = isCreate ? size + sizeof(size) : 0; 460 | 461 | // Create or get shared memory object 462 | int fd = shm_open(name.c_str(), oflag, mode); 463 | if (fd == -1) { 464 | switch(errno) { 465 | case EEXIST: // already exists 466 | case ENOENT: // not exists 467 | info.GetReturnValue().SetNull(); 468 | return; 469 | case ENAMETOOLONG: // length of name exceeds PATH_MAX 470 | return Nan::ThrowRangeError(strerror(errno)); 471 | default: 472 | return Nan::ThrowError(strerror(errno)); 473 | } 474 | } 475 | 476 | // Truncate 477 | int resTrunc; 478 | if (isCreate) { 479 | resTrunc = ftruncate(fd, realSize); 480 | if (resTrunc == -1) { 481 | switch(errno) { 482 | case EFBIG: // length exceeds max file size 483 | case EINVAL: // length exceeds max file size or < 0 484 | return Nan::ThrowRangeError(strerror(errno)); 485 | default: 486 | return Nan::ThrowError(strerror(errno)); 487 | } 488 | } 489 | } 490 | 491 | // Get size (not accurate, multiple of PAGE_SIZE = 4096) 492 | struct stat sb; 493 | int resStat; 494 | if (!isCreate) { 495 | resStat = fstat(fd, &sb); 496 | if (resStat == -1) { 497 | switch(errno) { 498 | default: 499 | return Nan::ThrowError(strerror(errno)); 500 | } 501 | } 502 | realSize = sb.st_size; 503 | } 504 | 505 | // Map shared memory object 506 | off_t offset = 0; 507 | int prot = PROT_READ | PROT_WRITE; 508 | void* res = mmap(NULL, realSize, prot, mmap_flags, fd, offset); 509 | if (res == MAP_FAILED) { 510 | switch(errno) { 511 | // case EBADF: // not valid fd 512 | // info.GetReturnValue().SetNull(); 513 | // return; 514 | case EINVAL: // length is bad, or flags does not comtain MAP_SHARED / MAP_PRIVATE / MAP_SHARED_VALIDATE 515 | return Nan::ThrowRangeError(strerror(errno)); 516 | default: 517 | return Nan::ThrowError(strerror(errno)); 518 | } 519 | } 520 | 521 | // Read/write actual buffer size at start of shared memory 522 | size_t* sizePtr = (size_t*) res; 523 | char* buf = (char*) res; 524 | buf += sizeof(size); 525 | if (isCreate) { 526 | *sizePtr = size; 527 | } else { 528 | size = *sizePtr; 529 | count = size / getSizeForShmBufferType(type); 530 | } 531 | 532 | // Write meta 533 | ShmMeta meta = { 534 | .type=SHM_TYPE_POSIX, .id=NO_SHMID, .memAddr=res, .memSize=realSize, .name=name, .isOwner=isCreate 535 | }; 536 | size_t metaInd = findShmSegmentInfo(meta); 537 | if (metaInd == NOT_FOUND_IND) { 538 | metaInd = addShmSegmentInfo(meta); 539 | } 540 | if (isCreate) { 541 | shmAllocatedBytes += realSize; 542 | shmMappedBytes += realSize; 543 | } else { 544 | shmMappedBytes += realSize; 545 | } 546 | 547 | // Don't save to meta 548 | close(fd); 549 | fd = 0; 550 | 551 | // Build and return buffer 552 | info.GetReturnValue().Set(Nan::NewTypedBuffer( 553 | buf, 554 | count, 555 | FreeCallback, 556 | reinterpret_cast(static_cast(metaInd)), 557 | type 558 | ).ToLocalChecked()); 559 | } 560 | 561 | NAN_METHOD(detach) { 562 | Nan::HandleScope scope; 563 | key_t key = Nan::To(info[0]).FromJust(); 564 | bool forceDestroy = Nan::To(info[1]).FromJust(); 565 | 566 | int shmid = shmget(key, 0, 0); 567 | if (shmid == -1) { 568 | switch(errno) { 569 | case ENOENT: // not exists 570 | case EIDRM: // scheduled for deletion 571 | info.GetReturnValue().Set(Nan::New(-1)); 572 | return; 573 | default: 574 | return Nan::ThrowError(strerror(errno)); 575 | } 576 | } else { 577 | ShmMeta meta = { 578 | .type=SHM_TYPE_SYSTEMV, .id=shmid, .memAddr=NULL, .memSize=0, .name="" 579 | }; 580 | size_t foundInd = findShmSegmentInfo(meta); 581 | if (foundInd != NOT_FOUND_IND) { 582 | int res = detachShmSegment(shmMeta[foundInd], forceDestroy); 583 | if (res != -1) 584 | removeShmSegmentInfo(foundInd); 585 | info.GetReturnValue().Set(Nan::New(res)); 586 | } else { 587 | //not found in meta array, means not created/opened by us 588 | int res = -1; 589 | if (forceDestroy) { 590 | res = detachShmSegment(meta, forceDestroy); 591 | } 592 | info.GetReturnValue().Set(Nan::New(res)); 593 | } 594 | } 595 | } 596 | 597 | NAN_METHOD(detachPosix) { 598 | Nan::HandleScope scope; 599 | if (!info[0]->IsString()) { 600 | return Nan::ThrowTypeError("Argument name must be a string"); 601 | } 602 | std::string name = (*Nan::Utf8String(info[0])); 603 | bool forceDestroy = Nan::To(info[1]).FromJust(); 604 | 605 | ShmMeta meta = { 606 | .type=SHM_TYPE_POSIX, .id=NO_SHMID, .memAddr=NULL, .memSize=0, .name=name 607 | }; 608 | size_t foundInd = findShmSegmentInfo(meta); 609 | if (foundInd != NOT_FOUND_IND) { 610 | int res = detachPosixShmObject(shmMeta[foundInd], forceDestroy); 611 | if (res != -1) 612 | removeShmSegmentInfo(foundInd); 613 | info.GetReturnValue().Set(Nan::New(res)); 614 | } else { 615 | //not found in meta array, means not created/opened by us 616 | int res = -1; 617 | if (forceDestroy) { 618 | res = detachPosixShmObject(meta, forceDestroy); 619 | } 620 | info.GetReturnValue().Set(Nan::New(res)); 621 | } 622 | } 623 | 624 | NAN_METHOD(detachAll) { 625 | int cnt = detachAllShm(); 626 | info.GetReturnValue().Set(Nan::New(cnt)); 627 | } 628 | 629 | NAN_METHOD(getTotalAllocatedSize) { 630 | info.GetReturnValue().Set(Nan::New(shmAllocatedBytes)); 631 | } 632 | 633 | NAN_METHOD(getTotalUsedSize) { 634 | info.GetReturnValue().Set(Nan::New(shmMappedBytes)); 635 | } 636 | 637 | // node::AtExit 638 | static void AtNodeExit(void*) { 639 | detachAllShm(); 640 | shmMeta.clear(); 641 | } 642 | 643 | // Init module 644 | #if NODE_MODULE_VERSION < NODE_16_0_MODULE_VERSION 645 | static void Init(Local target) { 646 | #else 647 | static void Init(Local target, Local module, void* priv) { 648 | #endif 649 | 650 | detachAllShm(); 651 | 652 | Nan::SetMethod(target, "get", get); 653 | Nan::SetMethod(target, "getPosix", getPosix); 654 | Nan::SetMethod(target, "detach", detach); 655 | Nan::SetMethod(target, "detachPosix", detachPosix); 656 | Nan::SetMethod(target, "detachAll", detachAll); 657 | Nan::SetMethod(target, "getTotalAllocatedSize", getTotalAllocatedSize); 658 | Nan::SetMethod(target, "getTotalUsedSize", getTotalUsedSize); 659 | 660 | Nan::Set(target, Nan::New("IPC_PRIVATE").ToLocalChecked(), Nan::New(IPC_PRIVATE)); 661 | Nan::Set(target, Nan::New("IPC_CREAT").ToLocalChecked(), Nan::New(IPC_CREAT)); 662 | Nan::Set(target, Nan::New("IPC_EXCL").ToLocalChecked(), Nan::New(IPC_EXCL)); 663 | 664 | Nan::Set(target, Nan::New("SHM_RDONLY").ToLocalChecked(), Nan::New(SHM_RDONLY)); 665 | 666 | Nan::Set(target, Nan::New("NODE_BUFFER_MAX_LENGTH").ToLocalChecked(), Nan::New(node::Buffer::kMaxLength)); 667 | 668 | Nan::Set(target, Nan::New("O_CREAT").ToLocalChecked(), Nan::New(O_CREAT)); 669 | Nan::Set(target, Nan::New("O_RDWR").ToLocalChecked(), Nan::New(O_RDWR)); 670 | Nan::Set(target, Nan::New("O_RDONLY").ToLocalChecked(), Nan::New(O_RDONLY)); 671 | Nan::Set(target, Nan::New("O_EXCL").ToLocalChecked(), Nan::New(O_EXCL)); 672 | Nan::Set(target, Nan::New("O_TRUNC").ToLocalChecked(), Nan::New(O_TRUNC)); 673 | 674 | Nan::Set(target, Nan::New("MAP_SHARED").ToLocalChecked(), Nan::New(MAP_SHARED)); 675 | Nan::Set(target, Nan::New("MAP_PRIVATE").ToLocalChecked(), Nan::New(MAP_PRIVATE)); 676 | 677 | Nan::Set(target, Nan::New("MAP_ANON").ToLocalChecked(), Nan::New(MAP_ANON)); 678 | Nan::Set(target, Nan::New("MAP_ANONYMOUS").ToLocalChecked(), Nan::New(MAP_ANONYMOUS)); 679 | Nan::Set(target, Nan::New("MAP_NORESERVE").ToLocalChecked(), Nan::New(MAP_NORESERVE)); 680 | //Nan::Set(target, Nan::New("MAP_32BIT").ToLocalChecked(), Nan::New(MAP_32BIT)); 681 | // Nan::Set(target, Nan::New("MAP_DENYWRITE").ToLocalChecked(), Nan::New(MAP_DENYWRITE)); 682 | // Nan::Set(target, Nan::New("MAP_GROWSDOWN").ToLocalChecked(), Nan::New(MAP_GROWSDOWN)); 683 | // Nan::Set(target, Nan::New("MAP_HUGETLB").ToLocalChecked(), Nan::New(MAP_HUGETLB)); 684 | // Nan::Set(target, Nan::New("MAP_HUGE_2MB").ToLocalChecked(), Nan::New(MAP_HUGE_2MB)); 685 | // Nan::Set(target, Nan::New("MAP_HUGE_1GB").ToLocalChecked(), Nan::New(MAP_HUGE_1GB)); 686 | // Nan::Set(target, Nan::New("MAP_LOCKED").ToLocalChecked(), Nan::New(MAP_LOCKED)); 687 | // Nan::Set(target, Nan::New("MAP_NONBLOCK").ToLocalChecked(), Nan::New(MAP_NONBLOCK)); 688 | // Nan::Set(target, Nan::New("MAP_POPULATE").ToLocalChecked(), Nan::New(MAP_POPULATE)); 689 | // Nan::Set(target, Nan::New("MAP_STACK").ToLocalChecked(), Nan::New(MAP_STACK)); 690 | // Nan::Set(target, Nan::New("MAP_SYNC").ToLocalChecked(), Nan::New(MAP_SYNC)); 691 | // Nan::Set(target, Nan::New("MAP_UNINITIALIZED").ToLocalChecked(), Nan::New(MAP_UNINITIALIZED)); 692 | 693 | //enum ShmBufferType 694 | Nan::Set(target, Nan::New("SHMBT_BUFFER").ToLocalChecked(), Nan::New(SHMBT_BUFFER)); 695 | Nan::Set(target, Nan::New("SHMBT_INT8").ToLocalChecked(), Nan::New(SHMBT_INT8)); 696 | Nan::Set(target, Nan::New("SHMBT_UINT8").ToLocalChecked(), Nan::New(SHMBT_UINT8)); 697 | Nan::Set(target, Nan::New("SHMBT_UINT8CLAMPED").ToLocalChecked(), Nan::New(SHMBT_UINT8CLAMPED)); 698 | Nan::Set(target, Nan::New("SHMBT_INT16").ToLocalChecked(), Nan::New(SHMBT_INT16)); 699 | Nan::Set(target, Nan::New("SHMBT_UINT16").ToLocalChecked(), Nan::New(SHMBT_UINT16)); 700 | Nan::Set(target, Nan::New("SHMBT_INT32").ToLocalChecked(), Nan::New(SHMBT_INT32)); 701 | Nan::Set(target, Nan::New("SHMBT_UINT32").ToLocalChecked(), Nan::New(SHMBT_UINT32)); 702 | Nan::Set(target, Nan::New("SHMBT_FLOAT32").ToLocalChecked(), Nan::New(SHMBT_FLOAT32)); 703 | Nan::Set(target, Nan::New("SHMBT_FLOAT64").ToLocalChecked(), Nan::New(SHMBT_FLOAT64)); 704 | 705 | #if NODE_MODULE_VERSION < NODE_16_0_MODULE_VERSION 706 | node::AtExit(AtNodeExit); 707 | #else 708 | node::AddEnvironmentCleanupHook(target->GetIsolate(), AtNodeExit, nullptr); 709 | #endif 710 | } 711 | 712 | } 713 | } 714 | 715 | //------------------------------- 716 | 717 | NODE_MODULE(shm, node::node_shm::Init); 718 | -------------------------------------------------------------------------------- /src/node_shm.h: -------------------------------------------------------------------------------- 1 | #include "node.h" 2 | #include "node_buffer.h" 3 | #include "v8.h" 4 | #include "nan.h" 5 | #include "errno.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace node; 21 | using namespace v8; 22 | 23 | 24 | /* 25 | namespace imp { 26 | static const size_t kMaxLength = 0x3fffffff; 27 | } 28 | 29 | namespace node { 30 | namespace Buffer { 31 | // 2^31 for 64bit, 2^30 for 32bit 32 | static const unsigned int kMaxLength = 33 | sizeof(int32_t) == sizeof(intptr_t) ? 0x3fffffff : 0x7fffffff; 34 | } 35 | } 36 | */ 37 | 38 | #define SAFE_DELETE(a) if( (a) != NULL ) delete (a); (a) = NULL; 39 | #define SAFE_DELETE_ARR(a) if( (a) != NULL ) delete [] (a); (a) = NULL; 40 | 41 | 42 | enum ShmBufferType { 43 | SHMBT_BUFFER = 0, //for using Buffer instead of TypedArray 44 | SHMBT_INT8, 45 | SHMBT_UINT8, 46 | SHMBT_UINT8CLAMPED, 47 | SHMBT_INT16, 48 | SHMBT_UINT16, 49 | SHMBT_INT32, 50 | SHMBT_UINT32, 51 | SHMBT_FLOAT32, 52 | SHMBT_FLOAT64 53 | }; 54 | 55 | inline int getSizeForShmBufferType(ShmBufferType type) { 56 | size_t size1 = 0; 57 | switch(type) { 58 | case SHMBT_BUFFER: 59 | case SHMBT_INT8: 60 | case SHMBT_UINT8: 61 | case SHMBT_UINT8CLAMPED: 62 | size1 = 1; 63 | break; 64 | case SHMBT_INT16: 65 | case SHMBT_UINT16: 66 | size1 = 2; 67 | break; 68 | case SHMBT_INT32: 69 | case SHMBT_UINT32: 70 | case SHMBT_FLOAT32: 71 | size1 = 4; 72 | break; 73 | default: 74 | case SHMBT_FLOAT64: 75 | size1 = 8; 76 | break; 77 | } 78 | return size1; 79 | } 80 | 81 | 82 | namespace node { 83 | namespace Buffer { 84 | 85 | MaybeLocal NewTyped( 86 | Isolate* isolate, 87 | char* data, 88 | size_t length 89 | #if NODE_MODULE_VERSION > IOJS_2_0_MODULE_VERSION 90 | , node::Buffer::FreeCallback callback 91 | #else 92 | , node::smalloc::FreeCallback callback 93 | #endif 94 | , void *hint 95 | , ShmBufferType type = SHMBT_FLOAT64 96 | ); 97 | 98 | } 99 | } 100 | 101 | 102 | namespace Nan { 103 | 104 | inline MaybeLocal NewTypedBuffer( 105 | char *data 106 | , size_t length 107 | #if NODE_MODULE_VERSION > IOJS_2_0_MODULE_VERSION 108 | , node::Buffer::FreeCallback callback 109 | #else 110 | , node::smalloc::FreeCallback callback 111 | #endif 112 | , void *hint 113 | , ShmBufferType type = SHMBT_FLOAT64 114 | ); 115 | 116 | } 117 | 118 | 119 | namespace node { 120 | namespace node_shm { 121 | 122 | /** 123 | * Create or get System V shared memory segment 124 | * Params: 125 | * key_t key 126 | * size_t count - count of elements, not bytes 127 | * int shmflg - flags for shmget() 128 | * int at_shmflg - flags for shmat() 129 | * enum ShmBufferType type 130 | * Returns buffer or typed array, depends on input param type 131 | * If not exists/alreeady exists, returns null 132 | */ 133 | NAN_METHOD(get); 134 | 135 | /** 136 | * Create or get POSIX shared memory object 137 | * Params: 138 | * String name 139 | * size_t count - count of elements, not bytes 140 | * int oflag - flag for shm_open() 141 | * mode_t mode - mode for shm_open() 142 | * int mmap_flags - flags for mmap() 143 | * enum ShmBufferType type 144 | * Returns buffer or typed array, depends on input param type 145 | * If not exists/alreeady exists, returns null 146 | */ 147 | NAN_METHOD(getPosix); 148 | 149 | /** 150 | * Detach System V shared memory segment 151 | * Params: 152 | * key_t key 153 | * bool force - true to destroy even there are other processed uses this segment 154 | * Returns 0 if deleted, or count of left attaches, or -1 if not exists 155 | */ 156 | NAN_METHOD(detach); 157 | 158 | /** 159 | * Detach POSIX shared memory object 160 | * Params: 161 | * String name 162 | * bool force - true to destroy 163 | * Returns 0 if deleted, 1 if detached, -1 if not exists 164 | */ 165 | NAN_METHOD(detachPosix); 166 | 167 | /** 168 | * Detach all created and getted shared memory segments and objects 169 | * Returns count of destroyed System V segments 170 | */ 171 | NAN_METHOD(detachAll); 172 | 173 | /** 174 | * Get total size of all *created* shared memory in bytes 175 | */ 176 | NAN_METHOD(getTotalAllocatedSize); 177 | 178 | /** 179 | * Get total size of all *used* shared memory in bytes 180 | */ 181 | NAN_METHOD(getTotalUsedSize); 182 | 183 | /** 184 | * Constants to be exported: 185 | * IPC_PRIVATE, IPC_CREAT, IPC_EXCL 186 | * SHM_RDONLY 187 | * NODE_BUFFER_MAX_LENGTH (count of elements, not bytes) 188 | * O_CREAT, O_RDWR, O_RDONLY, O_EXCL, O_TRUNC 189 | * enum ShmBufferType: 190 | * SHMBT_BUFFER, SHMBT_INT8, SHMBT_UINT8, SHMBT_UINT8CLAMPED, 191 | * SHMBT_INT16, SHMBT_UINT16, SHMBT_INT32, SHMBT_UINT32, 192 | * SHMBT_FLOAT32, SHMBT_FLOAT64 193 | */ 194 | 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster'); 2 | const shm = require('../index.js'); 3 | const assert = require('assert'); 4 | 5 | const key1 = 12345678; 6 | const unexistingKey = 1234567891; 7 | const posixKey = '/1234567'; 8 | 9 | let buf, arr; 10 | if (cluster.isMaster) { 11 | cleanup(); 12 | 13 | // Assert that creating shm with same key twice will fail 14 | const a = shm.create(10, 'Float32Array', key1); //SYSV 15 | const b = shm.create(10, 'Float32Array', key1); //SYSV 16 | assert.equal(shm.getTotalCreatedSize(), 10*4); 17 | assert.equal(shm.getTotalSize(), 10*4); 18 | assert(a instanceof Float32Array); 19 | assert.equal(a.key, key1); 20 | assert.equal(b, null); 21 | 22 | // Detach and destroy 23 | let attachesCnt = shm.detach(a.key); 24 | assert.equal(attachesCnt, 0); 25 | assert.equal(shm.getTotalSize(), 0); 26 | assert.equal(shm.getTotalCreatedSize(), 0); 27 | 28 | // Assert that getting shm by unexisting key will fail 29 | const c = shm.get(unexistingKey, 'Buffer'); 30 | assert(c === null); 31 | 32 | // Test using shm between 2 node processes 33 | buf = shm.create(4096); //4KB, SYSV 34 | assert.equal(shm.getTotalSize(), 4096); 35 | arr = shm.create(10000, 'Float32Array', posixKey); //1M floats, POSIX 36 | assert.equal(shm.getTotalSize(), 4096 + 10000*4+8); // extra 8 bytes for size of POSIX buffer (for 64bit system) 37 | assert.equal(shm.getTotalCreatedSize(), 4096 + 10000*4+8); 38 | assert(arr && typeof arr.key === 'undefined'); 39 | //bigarr = shm.create(1000*1000*1000*1.5, 'Float32Array'); //6Gb 40 | assert.equal(arr.length, 10000); 41 | assert.equal(arr.byteLength, 4*10000); 42 | buf[0] = 1; 43 | arr[0] = 10.0; 44 | //bigarr[bigarr.length-1] = 6.66; 45 | console.log('[Master] Typeof buf:', buf.constructor.name, 46 | 'Typeof arr:', arr.constructor.name); 47 | 48 | const worker = cluster.fork(); 49 | worker.on('online', function() { 50 | this.send({ 51 | msg: 'shm', 52 | bufKey: buf.key, 53 | arrKey: posixKey, //arr.key, 54 | //bigarrKey: bigarr.key, 55 | }); 56 | let i = 0; 57 | setInterval(function() { 58 | buf[0] += 1; 59 | arr[0] /= 2; 60 | console.log(i + ' [Master] Set buf[0]=', buf[0], 61 | ' arr[0]=', arr ? arr[0] : null); 62 | i++; 63 | if (i == 5) { 64 | groupSuicide(); 65 | } 66 | }, 500); 67 | }); 68 | 69 | process.on('exit', cleanup); 70 | } else { 71 | process.on('message', function(data) { 72 | const msg = data.msg; 73 | if (msg == 'shm') { 74 | buf = shm.get(data.bufKey); 75 | arr = shm.get(data.arrKey, 'Float32Array'); 76 | assert.equal(shm.getTotalCreatedSize(), 0); 77 | // actual size of POSIX object can be multiple of PAGE_SIZE = 4096, but not for all OS 78 | assert(shm.getTotalSize() == 4096 + 40960 || shm.getTotalSize() == 4096 + 10000*4+8); 79 | 80 | //bigarr = shm.get(data.bigarrKey, 'Float32Array'); 81 | console.log('[Worker] Typeof buf:', buf.constructor.name, 82 | 'Typeof arr:', arr.constructor.name); 83 | //console.log('[Worker] Test bigarr: ', bigarr[bigarr.length-1]); 84 | let i = 0; 85 | setInterval(function() { 86 | console.log(i + ' [Worker] Get buf[0]=', buf[0], 87 | ' arr[0]=', arr ? arr[0] : null); 88 | i++; 89 | if (i == 2) { 90 | shm.detach(data.arrKey); 91 | arr = null; //otherwise process will drop 92 | } 93 | }, 500); 94 | } else if (msg == 'exit') { 95 | process.exit(); 96 | } 97 | }); 98 | } 99 | 100 | function cleanup() { 101 | try { 102 | if (shm.destroy(key1)) { 103 | console.log(`Destroyed System V shared memory segment with key ${key1}`); 104 | } 105 | } catch(_e) {} 106 | try { 107 | if (shm.destroy(posixKey)) { 108 | console.log(`Destroyed POSIX shared memory object with name ${posixKey}`); 109 | } 110 | } catch(_e) {} 111 | assert.equal(shm.getTotalSize(), 0); 112 | }; 113 | 114 | function groupSuicide() { 115 | if (cluster.isMaster) { 116 | for (const id in cluster.workers) { 117 | cluster.workers[id].send({ msg: 'exit'}); 118 | cluster.workers[id].destroy(); 119 | } 120 | process.exit(); 121 | } 122 | } 123 | 124 | /** 125 | * Output 126 | * 127 | 128 | [Master] Typeof buf: Buffer Typeof arr: Float32Array 129 | [Worker] Typeof buf: Buffer Typeof arr: Float32Array 130 | 0 [Master] Set buf[0]= 2 arr[0]= 5 131 | 0 [Worker] Get buf[0]= 2 arr[0]= 5 132 | 1 [Master] Set buf[0]= 3 arr[0]= 2.5 133 | 1 [Worker] Get buf[0]= 3 arr[0]= 2.5 134 | 2 [Master] Set buf[0]= 4 arr[0]= 1.25 135 | 2 [Worker] Get buf[0]= 4 arr[0]= null 136 | 3 [Master] Set buf[0]= 5 arr[0]= 0.625 137 | 3 [Worker] Get buf[0]= 5 arr[0]= null 138 | 4 [Master] Set buf[0]= 6 arr[0]= 0.3125 139 | shm segments destroyed: 1 140 | Destroyed POSIX memory object with key /1234567 141 | 142 | */ 143 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["node"], 4 | "strict": true, 5 | "baseUrl": "..", 6 | "skipLibCheck": true 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/typings.ts: -------------------------------------------------------------------------------- 1 | import * as shm from '../index' 2 | 3 | // typings:expect-error 4 | shm.create(); 5 | // typings:expect-error 6 | let fail1: Buffer = shm.create(1); 7 | let pass1: shm.Shm | null = shm.create(1); 8 | 9 | // typings:expect-error 10 | let fail2: Uint32Array = shm.create(1, 'Uint32Array'); 11 | let pass2: shm.Shm | null = shm.create(1, 'Uint32Array'); 12 | 13 | let pass3: shm.Shm | null = shm.create(456, 'Float64Array', 1234); 14 | let pass4: shm.Shm | null = shm.create(456, 'Float64Array', '/test'); 15 | let pass5: shm.Shm | null = shm.create(456, 'Float64Array', '/test', '660'); 16 | 17 | // typings:expect-error 18 | let fail3: Buffer = shm.get(123); 19 | let pass6 = shm.get(123); 20 | if (pass3) pass3.key as number; 21 | 22 | // typings:expect-error 23 | let fail4: Float64Array = shm.get(456, 'Float64Array'); 24 | let pass7: shm.Shm | null = shm.get(456, 'Float64Array'); 25 | 26 | // typings:expect-error 27 | shm.detach(); 28 | shm.detach(123) as number; 29 | shm.detach(123, true) as number; 30 | shm.detach('/test') as number; 31 | shm.detach('/test', true) as number; 32 | 33 | // typings:expect-error 34 | shm.destroy(); 35 | shm.destroy(123) as boolean; 36 | shm.destroy('/test') as boolean; 37 | 38 | shm.detachAll() as number; 39 | shm.getTotalSize() as number; 40 | shm.LengthMax as number; 41 | --------------------------------------------------------------------------------