Skip to content

Commit 9faa231

Browse files
indutnytrevnorris
authored andcommitted
001: initial C++ Streams proposal
1 parent d8eb50e commit 9faa231

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed

000-index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# Node.js Enhancement Proposals
2+
3+
* [Public C++ Streams][001-public-stream-base.md]

001-public-stream-base.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
| Title | Make C++ Stream APIs public |
2+
|--------|-----------------------------|
3+
| Author | @indutny |
4+
| Status | REJECTED |
5+
| Date | 2015-12-17 19:39:46 |
6+
7+
## Description
8+
9+
Currently we use so called `StreamBase` APIs through the core to implement
10+
various performance-critical networking protocols with maximum efficiency.
11+
It is evident that this performance improvement could be beneficial for
12+
user-land C++ addons too.
13+
14+
I propose to make this APIs public, introducing C++ interface similar to the
15+
following one (simplified):
16+
17+
## Interface
18+
19+
```C++
20+
class Stream {
21+
public:
22+
//
23+
// Low-level part
24+
//
25+
26+
// Perform protocol-specific shutdown (EOF)
27+
virtual int DoShutdown(ShutdownWrap* req_wrap) = 0;
28+
29+
// Write data asynchronously
30+
virtual int DoWrite(WriteWrap* w,
31+
uv_buf_t* bufs,
32+
size_t count,
33+
uv_stream_t* send_handle) = 0;
34+
35+
// **(optional)**
36+
// Try to write provided data immediately without blocking, if not
37+
// possible to do - should return `0` if no data was written, should decrement
38+
// `count` and change pointers/length in `bufs` if some data was written.
39+
virtual int DoTryWrite(uv_buf_t** bufs, size_t* count);
40+
41+
//
42+
// High-level part
43+
//
44+
45+
// Cast to the deepest class. Just a stub, probably does not need to be
46+
// here.
47+
virtual void* Cast() = 0;
48+
49+
// Return `true` or `false` depending on liveness of the underlying
50+
// resource. If Stream is not alive - all operations on it will result in
51+
// `EINVAL`
52+
virtual bool IsAlive() = 0;
53+
54+
// Return `true` if stream is currently closing. Closed streams should return
55+
// `false` when calling `IsAlive`
56+
virtual bool IsClosing() = 0;
57+
58+
// **optional**, if `true` - `send_handle` may be not `nullptr` in `DoWrite`
59+
virtual bool IsIPCPipe();
60+
61+
// **optional**, return fd
62+
virtual int GetFD();
63+
64+
// Start reading and emitting data
65+
virtual int ReadStart() = 0;
66+
67+
// Stop reading and emitting data (immediately)
68+
virtual int ReadStop() = 0;
69+
70+
protected:
71+
// One of these must be implemented
72+
virtual AsyncWrap* GetAsyncWrap();
73+
virtual v8::Local<v8::Object> GetObject();
74+
};
75+
```
76+
77+
## Public APIs
78+
79+
Public facing APIs have two sides: JS and C++.
80+
81+
### JS
82+
83+
```js
84+
stream.fd; // stream's fd or -1
85+
stream._externalStream; // External pointer to the deepest C++ class
86+
stream.readStart();
87+
stream.readStop();
88+
stream.shutdown(req);
89+
stream.writev(req, [ ..., chunk, size, ... ]);
90+
stream.writeBuffer(req, buffer);
91+
stream.writeAsciiString(req, string);
92+
stream.writeUtf8String(req, string);
93+
stream.writeUcs2String(req, string);
94+
stream.writeBinaryString(req, string);
95+
```
96+
97+
### C++
98+
99+
All of the C++ interface methods may be called. Additionally following methods
100+
are available:
101+
102+
```C++
103+
class Stream {
104+
public:
105+
//
106+
// Low-level APIs
107+
//
108+
109+
// Invoke `after_write_cb`
110+
inline void OnAfterWrite(WriteWrap* w);
111+
112+
// Invoke `alloc_cb`
113+
inline void OnAlloc(size_t size, uv_buf_t* buf);
114+
115+
// Invoke `read_cb`
116+
inline void OnRead(size_t nread,
117+
const uv_buf_t* buf,
118+
uv_handle_type pending = UV_UNKNOWN_HANDLE);
119+
120+
// Override and get callbacks
121+
inline void set_after_write_cb(Callback<AfterWriteCb> c);
122+
inline void set_alloc_cb(Callback<AllocCb> c);
123+
inline void set_read_cb(Callback<ReadCb> c);
124+
inline Callback<AfterWriteCb> after_write_cb();
125+
inline Callback<AllocCb> alloc_cb();
126+
inline Callback<ReadCb> read_cb();
127+
128+
//
129+
// High-level APIs
130+
//
131+
132+
// Add JS API methods to the target `FunctionTemplate`
133+
// NOTE: `flags` control some optional methods like: `shutdown` and `writev`
134+
static inline void AddMethods(Environment* env,
135+
v8::Local<v8::FunctionTemplate> target,
136+
int flags = kFlagNone);
137+
138+
// Emit data to the JavaScript listeners
139+
void EmitData(ssize_t nread,
140+
v8::Local<v8::Object> buf,
141+
v8::Local<v8::Object> handle);
142+
143+
// Mark stream as consumed
144+
inline void Consume();
145+
146+
// TODO(indutny): add this to node.js core
147+
inline void Unconsume();
148+
};
149+
```
150+
151+
## Putting things together
152+
153+
This APIs could be used for implementing high-performance protocols in a
154+
following way:
155+
156+
Each TCP socket has a `_handle` property, which is actually a C++ `Stream`
157+
instance. One may wrap this `_handle` into a custom C++ `Stream` instance, and
158+
replace the `_handle` property with it. This stream could override `read`,
159+
`alloc`, `after_write` callbacks to hook up the incoming data from the wrapped
160+
stream.
161+
162+
Calling `DoWrite` and friends on the wrapped stream will result in performing
163+
actual TCP writes.
164+
165+
It is easy to see that it could be wrapped this way several times, creating
166+
levels of abstraction without leaving C++!

0 commit comments

Comments
 (0)