@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.
- package/LICENSE +39 -0
- package/README.md +122 -0
- package/index.js +175 -0
- 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 [](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
|
+
}
|