@ohos-ports/fd 0.0.3-beta.1

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 (4) hide show
  1. package/LICENSE +39 -0
  2. package/README.md +122 -0
  3. package/index.js +175 -0
  4. package/package.json +36 -0
package/LICENSE ADDED
@@ -0,0 +1,39 @@
1
+ Copyright 2012, Rod Vagg (the "Original Author")
2
+ All rights reserved.
3
+
4
+ MIT +no-false-attribs License
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the "Software"), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ Distributions of all or part of the Software intended to be used
19
+ by the recipients as they would use the unmodified Software,
20
+ containing modifications that substantially alter, remove, or
21
+ disable functionality of the Software, outside of the documented
22
+ configuration mechanisms provided by the Software, shall be
23
+ modified such that the Original Author's bug reporting email
24
+ addresses and urls are either replaced with the contact information
25
+ of the parties responsible for the changes, or removed entirely.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
29
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
31
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
32
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
33
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
34
+ OTHER DEALINGS IN THE SOFTWARE.
35
+
36
+
37
+ Except where noted, this license applies to any and all software
38
+ programs and associated documentation files created by the
39
+ Original Author, when distributed with the Software.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # fd [![Build Status](https://secure.travis-ci.org/rvagg/node-fd.png)](http://travis-ci.org/rvagg/node-fd)
2
+
3
+ File descriptor manager for Node.js. *Available in npm as <strong>fd</strong>*.
4
+
5
+ **fd** manages `fs.open()` and `fs.close()` calls safely for you where there may be timing issues related to multiple-use of the same descriptor.
6
+
7
+ **fd** provides `checkin()` and `checkout()` functions so your application can register its intent to use a file descriptor after it's been opened and then register that it has finished with the descriptor so that any pending `fs.close()` operations may be performed.
8
+
9
+ **fd** naturally couples with [async-cache](https://github.com/isaacs/async-cache) to provide a safe pool of file descriptors.
10
+
11
+ ## Example
12
+
13
+ Lets make a static resource web server! This example can be found in the *example/* directory of this repository.
14
+
15
+ We use [async-cache](https://github.com/isaacs/async-cache) to cache both `fs.sync()` calls and `fd`s, but we hook it up to **fd** so we can safely manage opens and closes.
16
+
17
+ ```js
18
+ const fdman = require('fd')()
19
+ , http = require('http')
20
+ , fs = require('fs')
21
+ , path = require('path')
22
+ , AC = require('async-cache')
23
+ , mime = require('mime')
24
+
25
+ , ROOT = path.join(__dirname, 'public')
26
+
27
+ // an async cache for fs.stat calls, fresh for 10s
28
+ , statCache = AC({
29
+ max : 100
30
+ , maxAge : 10000
31
+ , load : function (path, callback) {
32
+ fs.stat(path, callback)
33
+ }
34
+ })
35
+
36
+ // an async cache for fds, fresh for 10s
37
+ , fdCache = AC({
38
+ max : 100
39
+ , maxAge : 10000
40
+ // use fdman to open & close
41
+ , load : fdman.open.bind(fdman)
42
+ , dispose : fdman.close.bind(fdman)
43
+ })
44
+
45
+ , serveError = function (res) {
46
+ res.statusCode = 404
47
+ res.setHeader('content-type', 'text/plain')
48
+ res.end(http.STATUS_CODES[res.statusCode] + '\n')
49
+ }
50
+
51
+ http.createServer(function (req, res) {
52
+ var p = path.join(ROOT, req.url)
53
+
54
+ // get a fs.stat for this file
55
+ statCache.get(p, function (err, stat) {
56
+ if (err || !stat.isFile())
57
+ return serveError(res)
58
+
59
+ // get an fd for this file
60
+ fdCache.get(p, function (err, fd) {
61
+ var mimeType = mime.lookup(path.extname(p))
62
+ // get a safe checkin function from fdman that
63
+ // we could safely all multiple times for this single
64
+ // checkout
65
+ , checkin = fdman.checkinfn(p, fd)
66
+
67
+ // check out the fd for use
68
+ fdman.checkout(p, fd)
69
+
70
+ res.setHeader(
71
+ 'content-type'
72
+ // don't force download, just show it
73
+ , mimeType != 'application/octet-stream' ? mimeType : 'text/plain'
74
+ )
75
+
76
+ // stream from the fd to the response
77
+ var st = fs.createReadStream(p, { fd: fd, start: 0, end: stat.size })n
78
+ .on('end', checkin)
79
+ .on('error', checkin)
80
+
81
+ // override destroy so we don't close the fd
82
+ st.destroy = function () {}
83
+
84
+ st.pipe(res)
85
+
86
+ })
87
+ })
88
+ }).listen(8080)
89
+ ```
90
+
91
+ ## API
92
+
93
+ ### fd()
94
+ Create a new instance of **fd**. Typically called with `var fdman = require('fd')()`. You can have multiple, separate instances of **fd** operating at the same time, hence the need to instantiate.
95
+
96
+ ### fdman.open(path, callback)
97
+ Equivalent to `fs.open(path, callback)`, you'll get back an `err` and `fd` parameters but the descriptor will go into the managed pool.
98
+
99
+ ### fdman.close(path, fd)
100
+ Will call `fs.close(fd)` *only when the `fd` is no longer in use*. i.e. it will wait till all current uses have been checked in (see below).
101
+
102
+ ### fdman.checkout(path, fd)
103
+ Called when your application may need to use the `fd`. This should be called as early as possible, even if your application may not end up using it.
104
+
105
+ It is important to perform a `checkout()` as soon as you have a reference to the file descriptor if you may be using it, otherwise an asynchronous call may interrupt and call `close()` before you use it. You *don't have to use the `fd`* to register your intent to use it, as long as you eventually call `checkin()`.
106
+
107
+ ### fdman.checkin(path, fd)
108
+ Register with **fd** that you have finished using the descriptor and it can be safely closed if need be.
109
+
110
+ The descriptor may not need to be closed or there may be other uses of the descriptor currently checked out so a `checkin()` won't automatically lead to a `close()`.
111
+
112
+ ### fdman.checkinfn(path, fd)
113
+ Returns a function that, when called, will safely perform a `checkin()` for you on the given path and descriptor. An important property of the function is that it will only perform a single `checkin()` regardless of how many times it is called.
114
+
115
+ This returned function is helpful for calling `checkin()` from multiple points in your application, such as in case of error, and you don't need to worry about whether it's been previously called for the current `checkout()`.
116
+
117
+ See the example above how this can be used.
118
+
119
+
120
+ ## Licence
121
+
122
+ fd is Copyright (c) 2012 Rod Vagg [@rvagg](https://twitter.com/rvagg) and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.
package/index.js ADDED
@@ -0,0 +1,175 @@
1
+ const fs = require('fs')
2
+
3
+ var totalOpenFds = 0 // across all instances
4
+
5
+ // the reason we use a combination of path+fd to store references
6
+ // in this._fds is that it is possible to have multiple fds
7
+ // for the same file simultaneously. Particularly in the situation
8
+ // where an fd is pending for a close but hasn't been checked back
9
+ // in by all clients
10
+ , key = function (path, fd) {
11
+ return fd + ':' + path
12
+ }
13
+
14
+ // actually close the fd and forget all bookkeeping for it
15
+ , cleanupFd = function (path, fd) {
16
+ var k = key(path, fd)
17
+
18
+ delete this._fds[k]
19
+ // only remove the path entry when it actually points at *this* fd,
20
+ // otherwise we would pull the rug out from under a newer fd that
21
+ // was opened for the same path while this one was being closed
22
+ if (this._fds[path] === fd)
23
+ delete this._fds[path]
24
+
25
+ if (this._pendingClose[k]) {
26
+ fs.close(fd, function () {})
27
+ totalOpenFds--
28
+ delete this._pendingClose[k]
29
+ delete this._everCheckedOut[k]
30
+ delete this._syncWindow[k]
31
+ delete this._syncCheckout[k]
32
+ }
33
+ }
34
+
35
+ // forget the bookkeeping for an fd that has gone idle *without* closing
36
+ // it: more checkouts may still follow (a close() request does not stop
37
+ // new checkouts), and closing it here - or in any timed "idle" window -
38
+ // races with those future checkouts. when such a close-then-checkout
39
+ // race is lost the OS re-uses the closed fd number for another file and
40
+ // the outstanding user reads the wrong file's contents. the fd is
41
+ // therefore deliberately left open and merely marked via _pendingClose
42
+ // so open() will hand out a fresh fd for the path instead of the old one
43
+ , forgetFd = function (path, fd) {
44
+ var k = key(path, fd)
45
+
46
+ delete this._fds[k]
47
+ if (this._fds[path] === fd)
48
+ delete this._fds[path]
49
+ }
50
+
51
+ , close = function (path, fd) {
52
+ // dispose of this fd when possible
53
+
54
+ var k = key(path, fd)
55
+
56
+ this._pendingClose[k] = fd
57
+ // nextTick needed to match the nextTick in an async-cache otherwise we may
58
+ // close it in the tick prior to it being actually needed
59
+ process.nextTick(function () {
60
+ // only ever *really* close an fd that has never been checked out:
61
+ // nobody can be using it and no future checkout can be pending for
62
+ // it, so this is the one case where closing is provably safe. for
63
+ // fds that have been checked out we cannot know whether another
64
+ // checkout (which may legitimately follow a close request) is still
65
+ // to come, and timer-based "idle grace" windows are unreliable under
66
+ // load (timers fire out of order / in bursts), so those fds are kept
67
+ // open for the lifetime of the manager and simply excluded from
68
+ // reuse by the _pendingClose marker
69
+ if (!this._fds[k] && !this._everCheckedOut[k])
70
+ cleanupFd.call(this, path, fd)
71
+ }.bind(this))
72
+ }
73
+
74
+ , open = function (path, cb) {
75
+ // open a new fd
76
+
77
+ // if the file is already open and in use but not with a close pending
78
+ // then just use that.
79
+ if (this._fds[path] && !this._pendingClose[key(path, this._fds[path])])
80
+ return cb(null, this._fds[path])
81
+
82
+ fs.open(path, 'r', function (er, fd) {
83
+ var k = key(path, fd)
84
+
85
+ if (!er) {
86
+ totalOpenFds++
87
+ this._fds[path] = fd
88
+ this._fds[key(path, fd)] = 0
89
+ // mark the current turn as the fd's "synchronous window": a
90
+ // checkout performed synchronously within this open callback (the
91
+ // documented open() -> checkout() -> close() -> checkin() pattern)
92
+ // is one whose lifetime the caller fully controls, so the pending
93
+ // close can be honoured the moment that checkout is checked back
94
+ // in. checkouts that happen in later turns (timers etc.) may be
95
+ // followed by yet more checkouts, so they opt out of that
96
+ this._syncWindow[k] = true
97
+ process.nextTick(function () {
98
+ delete this._syncWindow[k]
99
+ }.bind(this))
100
+ }
101
+
102
+ cb(er, fd)
103
+ }.bind(this))
104
+ }
105
+
106
+ , checkout = function (path, fd) {
107
+ // call whenever you *may* be about to use the fd, to ensure it's not cleaned up
108
+
109
+ var k = key(path, fd)
110
+ , count = this._fds[k] = (this._fds[k] || 0) + 1
111
+
112
+ this._fds[path] = fd
113
+ this._everCheckedOut[k] = true
114
+ if (this._syncWindow[k])
115
+ this._syncCheckout[k] = true
116
+ else
117
+ // an out-of-turn checkout means the fd may see yet more checkouts
118
+ // after a close() request, so the close may not be honoured at the
119
+ // next checkin (see checkin() and forgetFd())
120
+ this._syncCheckout[k] = false
121
+ }
122
+
123
+ , checkin = function (path, fd) {
124
+ // call sometime after checkout() when you know you're not using it
125
+
126
+ var k = key(path, fd)
127
+
128
+ if (this._fds[path] && --this._fds[k] === 0) {
129
+ if (this._pendingClose[k] && this._syncCheckout[k]) {
130
+ // documented pattern: open() -> checkout() in the open callback ->
131
+ // close() -> checkin(). every checkout of this fd happened inside
132
+ // the open callback, so once it's checked back in nothing can
133
+ // possibly use it anymore - close it right now
134
+ cleanupFd.call(this, path, fd)
135
+ } else {
136
+ // fd went idle, but it has seen (or may still see) checkouts from
137
+ // outside the open callback, so a pending close must not be
138
+ // honoured here - just forget the entries and keep the fd alive
139
+ forgetFd.call(this, path, fd)
140
+ }
141
+ }
142
+ }
143
+
144
+ , checkinfn = function (path, fd) {
145
+ // make a checkin function that can be safely called multiple times
146
+
147
+ var called = false
148
+ return function () {
149
+ if (!called) {
150
+ this.checkin(path, fd)
151
+ called = true
152
+ }
153
+ }.bind(this)
154
+ }
155
+
156
+ , FDManager = {
157
+ open : open
158
+ , close : close
159
+ , checkout : checkout
160
+ , checkin : checkin
161
+ , checkinfn : checkinfn
162
+ }
163
+
164
+ , create = function () {
165
+ return Object.create(FDManager, {
166
+ _fds : { value: Object.create(null) }
167
+ , _pendingClose : { value: Object.create(null) }
168
+ , _everCheckedOut : { value: Object.create(null) }
169
+ , _syncWindow : { value: Object.create(null) }
170
+ , _syncCheckout : { value: Object.create(null) }
171
+ })
172
+ }
173
+
174
+ module.exports = create
175
+ module.exports._totalOpenFds = totalOpenFds
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@ohos-ports/fd",
3
+ "description": "File descriptor manager",
4
+ "version": "0.0.3-beta.1",
5
+ "homepage": "https://github.com/rvagg/node-fd",
6
+ "authors": [
7
+ "Rod Vagg <rod@vagg.org> (https://github.com/rvagg)"
8
+ ],
9
+ "keywords": [
10
+ "fd",
11
+ "fs",
12
+ "descriptor",
13
+ "file"
14
+ ],
15
+ "main": "./index.js",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
19
+ "directory": "ports/fd/0.0.3"
20
+ },
21
+ "dependencies": {},
22
+ "devDependencies": {
23
+ "tap": "*",
24
+ "sinon": "*",
25
+ "mkfiletree": "*"
26
+ },
27
+ "scripts": {
28
+ "test": "tap test.js"
29
+ },
30
+ "license": "MIT",
31
+ "files": [
32
+ "index.js",
33
+ "LICENSE",
34
+ "README.md"
35
+ ]
36
+ }