Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit 832da2a

Browse files
Split out 'socket' hosting model into a separate optional NuGet package, since most developers won't need it
1 parent ebf5a18 commit 832da2a

24 files changed

+244
-135
lines changed

pack-local.sh

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ versionSuffix=$1
22
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
33
projects=(
44
./src/Microsoft.AspNetCore.NodeServices
5+
./src/Microsoft.AspNetCore.NodeServices.Sockets
56
./src/Microsoft.AspNetCore.SpaServices
67
./src/Microsoft.AspNetCore.AngularServices
78
./src/Microsoft.AspNetCore.ReactServices

samples/misc/LatencyTest/Program.cs

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.NodeServices;
6+
using Microsoft.AspNetCore.NodeServices.Sockets;
67
using Microsoft.Extensions.DependencyInjection;
78

89
namespace ConsoleApplication
@@ -16,6 +17,10 @@ public static void Main(string[] args) {
1617
// Set up the DI system
1718
var services = new ServiceCollection();
1819
services.AddNodeServices(options => {
20+
// To compare with Socket hosting, uncomment the following line
21+
// Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model
22+
//options.UseSocketHosting();
23+
1924
options.ProjectPath = Directory.GetCurrentDirectory();
2025
options.WatchFileExtensions = new string[] {}; // Don't watch anything
2126
});

samples/misc/LatencyTest/project.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"type": "platform"
1010
},
1111
"Microsoft.AspNetCore.NodeServices": "1.1.0-*",
12+
"Microsoft.AspNetCore.NodeServices.Sockets": "1.1.0-*",
1213
"Microsoft.Extensions.DependencyInjection": "1.1.0"
1314
},
1415
"frameworks": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/bin/
2+
/node_modules/
3+
yarn.lock

src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-socket.js renamed to src/Microsoft.AspNetCore.NodeServices.Sockets/Content/Node/entrypoint-socket.js

+96-97
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,81 @@
4444
/* 0 */
4545
/***/ function(module, exports, __webpack_require__) {
4646

47-
module.exports = __webpack_require__(7);
47+
module.exports = __webpack_require__(1);
48+
49+
50+
/***/ },
51+
/* 1 */
52+
/***/ function(module, exports, __webpack_require__) {
53+
54+
"use strict";
55+
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
56+
// but simplifies things for the consumer of this module.
57+
__webpack_require__(2);
58+
var net = __webpack_require__(3);
59+
var path = __webpack_require__(4);
60+
var readline = __webpack_require__(5);
61+
var ArgsUtil_1 = __webpack_require__(6);
62+
var ExitWhenParentExits_1 = __webpack_require__(7);
63+
var virtualConnectionServer = __webpack_require__(8);
64+
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
65+
// reference to Node's runtime 'require' function.
66+
var dynamicRequire = eval('require');
67+
// Signal to the .NET side when we're ready to accept invocations
68+
var server = net.createServer().on('listening', function () {
69+
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
70+
});
71+
// Each virtual connection represents a separate invocation
72+
virtualConnectionServer.createInterface(server).on('connection', function (connection) {
73+
readline.createInterface(connection, null).on('line', function (line) {
74+
try {
75+
// Get a reference to the function to invoke
76+
var invocation = JSON.parse(line);
77+
var invokedModule = dynamicRequire(path.resolve(process.cwd(), invocation.moduleName));
78+
var invokedFunction = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
79+
// Prepare a callback for accepting non-streamed JSON responses
80+
var hasInvokedCallback_1 = false;
81+
var invocationCallback = function (errorValue, successValue) {
82+
if (hasInvokedCallback_1) {
83+
throw new Error('Cannot supply more than one result. The callback has already been invoked,'
84+
+ ' or the result stream has already been accessed');
85+
}
86+
hasInvokedCallback_1 = true;
87+
connection.end(JSON.stringify({
88+
result: successValue,
89+
errorMessage: errorValue && (errorValue.message || errorValue),
90+
errorDetails: errorValue && (errorValue.stack || null)
91+
}));
92+
};
93+
// Also support streamed binary responses
94+
Object.defineProperty(invocationCallback, 'stream', {
95+
enumerable: true,
96+
get: function () {
97+
hasInvokedCallback_1 = true;
98+
return connection;
99+
}
100+
});
101+
// Actually invoke it, passing through any supplied args
102+
invokedFunction.apply(null, [invocationCallback].concat(invocation.args));
103+
}
104+
catch (ex) {
105+
connection.end(JSON.stringify({
106+
errorMessage: ex.message,
107+
errorDetails: ex.stack
108+
}));
109+
}
110+
});
111+
});
112+
// Begin listening now. The underlying transport varies according to the runtime platform.
113+
// On Windows it's Named Pipes; on Linux/OSX it's Domain Sockets.
114+
var useWindowsNamedPipes = /^win/.test(process.platform);
115+
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
116+
var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
117+
server.listen(listenAddress);
118+
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid));
48119

49120

50121
/***/ },
51-
/* 1 */,
52122
/* 2 */
53123
/***/ function(module, exports) {
54124

@@ -90,14 +160,25 @@
90160

91161

92162
/***/ },
93-
/* 3 */,
163+
/* 3 */
164+
/***/ function(module, exports) {
165+
166+
module.exports = require("net");
167+
168+
/***/ },
94169
/* 4 */
95170
/***/ function(module, exports) {
96171

97172
module.exports = require("path");
98173

99174
/***/ },
100175
/* 5 */
176+
/***/ function(module, exports) {
177+
178+
module.exports = require("readline");
179+
180+
/***/ },
181+
/* 6 */
101182
/***/ function(module, exports) {
102183

103184
"use strict";
@@ -123,7 +204,7 @@
123204

124205

125206
/***/ },
126-
/* 6 */
207+
/* 7 */
127208
/***/ function(module, exports) {
128209

129210
/*
@@ -189,96 +270,13 @@
189270
}
190271

191272

192-
/***/ },
193-
/* 7 */
194-
/***/ function(module, exports, __webpack_require__) {
195-
196-
"use strict";
197-
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
198-
// but simplifies things for the consumer of this module.
199-
__webpack_require__(2);
200-
var net = __webpack_require__(8);
201-
var path = __webpack_require__(4);
202-
var readline = __webpack_require__(9);
203-
var ArgsUtil_1 = __webpack_require__(5);
204-
var ExitWhenParentExits_1 = __webpack_require__(6);
205-
var virtualConnectionServer = __webpack_require__(10);
206-
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
207-
// reference to Node's runtime 'require' function.
208-
var dynamicRequire = eval('require');
209-
// Signal to the .NET side when we're ready to accept invocations
210-
var server = net.createServer().on('listening', function () {
211-
console.log('[Microsoft.AspNetCore.NodeServices:Listening]');
212-
});
213-
// Each virtual connection represents a separate invocation
214-
virtualConnectionServer.createInterface(server).on('connection', function (connection) {
215-
readline.createInterface(connection, null).on('line', function (line) {
216-
try {
217-
// Get a reference to the function to invoke
218-
var invocation = JSON.parse(line);
219-
var invokedModule = dynamicRequire(path.resolve(process.cwd(), invocation.moduleName));
220-
var invokedFunction = invocation.exportedFunctionName ? invokedModule[invocation.exportedFunctionName] : invokedModule;
221-
// Prepare a callback for accepting non-streamed JSON responses
222-
var hasInvokedCallback_1 = false;
223-
var invocationCallback = function (errorValue, successValue) {
224-
if (hasInvokedCallback_1) {
225-
throw new Error('Cannot supply more than one result. The callback has already been invoked,'
226-
+ ' or the result stream has already been accessed');
227-
}
228-
hasInvokedCallback_1 = true;
229-
connection.end(JSON.stringify({
230-
result: successValue,
231-
errorMessage: errorValue && (errorValue.message || errorValue),
232-
errorDetails: errorValue && (errorValue.stack || null)
233-
}));
234-
};
235-
// Also support streamed binary responses
236-
Object.defineProperty(invocationCallback, 'stream', {
237-
enumerable: true,
238-
get: function () {
239-
hasInvokedCallback_1 = true;
240-
return connection;
241-
}
242-
});
243-
// Actually invoke it, passing through any supplied args
244-
invokedFunction.apply(null, [invocationCallback].concat(invocation.args));
245-
}
246-
catch (ex) {
247-
connection.end(JSON.stringify({
248-
errorMessage: ex.message,
249-
errorDetails: ex.stack
250-
}));
251-
}
252-
});
253-
});
254-
// Begin listening now. The underlying transport varies according to the runtime platform.
255-
// On Windows it's Named Pipes; on Linux/OSX it's Domain Sockets.
256-
var useWindowsNamedPipes = /^win/.test(process.platform);
257-
var parsedArgs = ArgsUtil_1.parseArgs(process.argv);
258-
var listenAddress = (useWindowsNamedPipes ? '\\\\.\\pipe\\' : '/tmp/') + parsedArgs.listenAddress;
259-
server.listen(listenAddress);
260-
ExitWhenParentExits_1.exitWhenParentExits(parseInt(parsedArgs.parentPid));
261-
262-
263273
/***/ },
264274
/* 8 */
265-
/***/ function(module, exports) {
266-
267-
module.exports = require("net");
268-
269-
/***/ },
270-
/* 9 */
271-
/***/ function(module, exports) {
272-
273-
module.exports = require("readline");
274-
275-
/***/ },
276-
/* 10 */
277275
/***/ function(module, exports, __webpack_require__) {
278276

279277
"use strict";
280-
var events_1 = __webpack_require__(11);
281-
var VirtualConnection_1 = __webpack_require__(12);
278+
var events_1 = __webpack_require__(9);
279+
var VirtualConnection_1 = __webpack_require__(10);
282280
// Keep this in sync with the equivalent constant in the .NET code. Both sides split up their transmissions into frames with this max length,
283281
// and both will reject longer frames.
284282
var MaxFrameBodyLength = 16 * 1024;
@@ -460,13 +458,13 @@
460458

461459

462460
/***/ },
463-
/* 11 */
461+
/* 9 */
464462
/***/ function(module, exports) {
465463

466464
module.exports = require("events");
467465

468466
/***/ },
469-
/* 12 */
467+
/* 10 */
470468
/***/ function(module, exports, __webpack_require__) {
471469

472470
"use strict";
@@ -475,17 +473,18 @@
475473
function __() { this.constructor = d; }
476474
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
477475
};
478-
var stream_1 = __webpack_require__(13);
476+
var stream_1 = __webpack_require__(11);
479477
/**
480478
* Represents a virtual connection. Multiple virtual connections may be multiplexed over a single physical socket connection.
481479
*/
482480
var VirtualConnection = (function (_super) {
483481
__extends(VirtualConnection, _super);
484482
function VirtualConnection(_beginWriteCallback) {
485-
_super.call(this);
486-
this._beginWriteCallback = _beginWriteCallback;
487-
this._flowing = false;
488-
this._receivedDataQueue = [];
483+
var _this = _super.call(this) || this;
484+
_this._beginWriteCallback = _beginWriteCallback;
485+
_this._flowing = false;
486+
_this._receivedDataQueue = [];
487+
return _this;
489488
}
490489
VirtualConnection.prototype._read = function () {
491490
this._flowing = true;
@@ -516,7 +515,7 @@
516515

517516

518517
/***/ },
519-
/* 13 */
518+
/* 11 */
520519
/***/ function(module, exports) {
521520

522521
module.exports = require("stream");
+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.IO.Pipes;
33
using System.Threading.Tasks;
44

5-
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
5+
namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
66
{
77
internal class NamedPipeConnection : StreamConnection
88
{

src/Microsoft.AspNetCore.NodeServices/HostingModels/PhysicalConnections/StreamConnection.cs renamed to src/Microsoft.AspNetCore.NodeServices.Sockets/PhysicalConnections/StreamConnection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.IO;
33
using System.Threading.Tasks;
44

5-
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
5+
namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
66
{
77
internal abstract class StreamConnection : IDisposable
88
{
+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Net.Sockets;
33
using System.Threading.Tasks;
44

5-
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
5+
namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
66
{
77
internal class UnixDomainSocketConnection : StreamConnection
88
{
+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System.Net.Sockets;
44
using System.Text;
55

6-
namespace Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections
6+
namespace Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections
77
{
88
// From System.IO.Pipes/src/System/Net/Sockets/UnixDomainSocketEndPoint.cs (an internal class in System.IO.Pipes)
99
internal sealed class UnixDomainSocketEndPoint : EndPoint

src/Microsoft.AspNetCore.NodeServices/HostingModels/SocketNodeInstance.cs renamed to src/Microsoft.AspNetCore.NodeServices.Sockets/SocketNodeInstance.cs

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
using System;
2-
using System.Collections.Generic;
31
using System.IO;
42
using System.Text;
53
using System.Threading;
64
using System.Threading.Tasks;
7-
using Microsoft.AspNetCore.NodeServices.HostingModels.PhysicalConnections;
8-
using Microsoft.AspNetCore.NodeServices.HostingModels.VirtualConnections;
5+
using Microsoft.AspNetCore.NodeServices.HostingModels;
6+
using Microsoft.AspNetCore.NodeServices.Sockets.PhysicalConnections;
7+
using Microsoft.AspNetCore.NodeServices.Sockets.VirtualConnections;
98
using Microsoft.Extensions.Logging;
109
using Newtonsoft.Json;
1110
using Newtonsoft.Json.Serialization;
1211

13-
namespace Microsoft.AspNetCore.NodeServices.HostingModels
12+
namespace Microsoft.AspNetCore.NodeServices.Sockets
1413
{
1514
/// <summary>
1615
/// A specialisation of the OutOfProcessNodeInstance base class that uses a lightweight binary streaming protocol
@@ -77,7 +76,7 @@ protected override async Task<T> InvokeExportAsync<T>(NodeInvocationInfo invocat
7776
// wait for the same connection task. There's no reason why the first caller should have the
7877
// special ability to cancel the connection process in a way that would affect subsequent
7978
// callers. So, each caller just independently stops awaiting connection if that call is cancelled.
80-
await EnsureVirtualConnectionClientCreated().OrThrowOnCancellation(cancellationToken);
79+
await ThrowOnCancellation(EnsureVirtualConnectionClientCreated(), cancellationToken);
8180
}
8281

8382
// For each invocation, we open a new virtual connection. This gives an API equivalent to opening a new
@@ -213,6 +212,17 @@ private static string MakeNewCommandLineOptions(string listenAddress)
213212
return $"--listenAddress {listenAddress}";
214213
}
215214

215+
private static Task ThrowOnCancellation(Task task, CancellationToken cancellationToken)
216+
{
217+
return task.IsCompleted
218+
? task // If the task is already completed, no need to wrap it in a further layer of task
219+
: task.ContinueWith(
220+
_ => {}, // If the task completes, allow execution to continue
221+
cancellationToken,
222+
TaskContinuationOptions.ExecuteSynchronously,
223+
TaskScheduler.Default);
224+
}
225+
216226
#pragma warning disable 649 // These properties are populated via JSON deserialization
217227
private class RpcJsonResponse<TResult>
218228
{

0 commit comments

Comments
 (0)