Skip to content

Commit fbae14b

Browse files
committed
Merge pull request #149 from uProxy/trevj-delay-datachannel-closures
have DataChannel block close() call until all messages have been sent
2 parents a4d8d22 + df9bd5c commit fbae14b

File tree

5 files changed

+109
-19
lines changed

5 files changed

+109
-19
lines changed

Gruntfile.coffee

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ taskManager.add 'browserify_specs', [
5353
'browserify:buildToolsTaskmanagerSpec'
5454
'browserify:loggingSpec'
5555
'browserify:loggingProviderSpec'
56-
'browserify:webrtcSpec'
56+
'browserify:peerconnectionSpec'
57+
'browserify:datachannelSpec'
5758
'browserify:queueSpec'
5859
]
5960

@@ -215,7 +216,8 @@ module.exports = (grunt) ->
215216
handlerSpec: Rule.browserifySpec 'handler/queue'
216217
loggingProviderSpec: Rule.browserifySpec 'loggingprovider/loggingprovider'
217218
loggingSpec: Rule.browserifySpec 'logging/logging'
218-
webrtcSpec: Rule.browserifySpec 'webrtc/peerconnection'
219+
peerconnectionSpec: Rule.browserifySpec 'webrtc/peerconnection'
220+
datachannelSpec: Rule.browserifySpec 'webrtc/datachannel'
219221
queueSpec: Rule.browserifySpec 'queue/queue'
220222
# Browserify sample apps main freedom module and core environments
221223
copypasteFreedomChatFreedomModule: Rule.browserify 'samples/copypaste-freedom-chat/freedom-module'

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "uproxy-lib",
33
"description": "Shared libraries for uProxy projects.",
4-
"version": "21.0.0",
4+
"version": "22.0.0",
55
"repository": {
66
"type": "git",
77
"url": "https://github.com/uProxy/uproxy-lib"

src/webrtc/datachannel.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/// <reference path='../../../third_party/typings/es6-promise/es6-promise.d.ts' />
2+
/// <reference path='../../../third_party/typings/jasmine/jasmine.d.ts' />
3+
/// <reference path="../../../third_party/freedom-typings/rtcpeerconnection.d.ts" />
4+
5+
import MockFreedomRtcDataChannel = require('../freedom/mocks/mock-rtcdatachannel');
6+
7+
import freedomMocker = require('../freedom/mocks/mock-freedom-in-module-env');
8+
freedom = freedomMocker.makeMockFreedomInModuleEnv({
9+
'rtcdatachannel': () => { return new MockFreedomRtcDataChannel(); }
10+
});
11+
12+
import datachannel = require('./datachannel');
13+
14+
describe('DataChannel', function() {
15+
var mockRtcDataChannel :MockFreedomRtcDataChannel;
16+
17+
beforeEach(function() {
18+
mockRtcDataChannel = new MockFreedomRtcDataChannel();
19+
});
20+
21+
// Ensure that close() waits for all outgoing unsent messages to be sent:
22+
// https://github.com/uProxy/uproxy/issues/1218
23+
it('close waits for all data to be sent', (done) => {
24+
// The core.rtcdatachannel is initially open.
25+
spyOn(mockRtcDataChannel, 'getReadyState').and.returnValue(
26+
Promise.resolve('open'));
27+
28+
// Have core.rtcdatachannel emit an onclose event when its
29+
// close() method is invoked.
30+
var closeSpy = spyOn(mockRtcDataChannel, 'close').and.callFake(() => {
31+
mockRtcDataChannel.handleEvent('onclose');
32+
});
33+
34+
// Pretend the core.rtcdatachannel is buffering a lot of data.
35+
// This will cause DataChannel to buffer sends.
36+
var getBufferedAmountSpy = spyOn(mockRtcDataChannel, 'getBufferedAmount');
37+
getBufferedAmountSpy.and.callFake(() => {
38+
return Promise.resolve(datachannel.PC_QUEUE_LIMIT);
39+
});
40+
41+
var sendBufferSpy = spyOn(mockRtcDataChannel, 'sendBuffer');
42+
43+
datachannel.createFromRtcDataChannel(mockRtcDataChannel).then(
44+
(channel:datachannel.DataChannel) => {
45+
channel.send({
46+
buffer: new Uint8Array([0, 1, 2]).buffer
47+
});
48+
49+
channel.close();
50+
51+
// At this point, our message should not yet have been sent.
52+
// Now that we've requested the channel be closed, "unblock" the
53+
// core.rtcdatachannel to drain the message queue.
54+
expect(sendBufferSpy).not.toHaveBeenCalled();
55+
getBufferedAmountSpy.and.returnValue(Promise.resolve(0));
56+
57+
channel.onceClosed.then(() => {
58+
expect(sendBufferSpy).toHaveBeenCalled();
59+
done();
60+
});
61+
});
62+
});
63+
});

src/webrtc/datachannel.ts

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var CHUNK_SIZE = 1024 * 15;
2525
// the 16MB for Chrome 37+ and "100 messages" of previous versions mentioned):
2626
// https://code.google.com/p/webrtc/issues/detail?id=2866
2727
// CONSIDER: make it 0. There is no size in the spec.
28-
var PC_QUEUE_LIMIT = 1024 * 250;
28+
export var PC_QUEUE_LIMIT = 1024 * 250;
2929
// Javascript has trouble representing integers larger than 2^53. So we simply
3030
// don't support trying to send array's bigger than that.
3131
var MAX_MESSAGE_SIZE = Math.pow(2, 53);
@@ -70,9 +70,9 @@ export interface DataChannel {
7070
// cleared. There can only be one listener at a time. Pass null to unset.
7171
setOverflowListener(listener:(overflow:boolean) => void) : void;
7272

73-
// Closes this data channel.
73+
// Closes this data channel once all outgoing messages have been sent.
7474
// A channel cannot be re-opened once this has been called.
75-
close() : void;
75+
close() : Promise<void>;
7676

7777
toString() : string;
7878
}
@@ -109,6 +109,13 @@ export class DataChannelClass implements DataChannel {
109109
public onceOpened :Promise<void>;
110110
public onceClosed :Promise<void>;
111111

112+
// True iff close() has been called.
113+
private draining_ = false;
114+
private fulfillDrained_ :() => void;
115+
private onceDrained_ = new Promise((F, R) => {
116+
this.fulfillDrained_ = F;
117+
});
118+
112119
private opennedSuccessfully_ :boolean;
113120
private rejectOpened_ :(e:Error) => void;
114121

@@ -119,10 +126,8 @@ export class DataChannelClass implements DataChannel {
119126

120127
// |rtcDataChannel_| is the freedom rtc data channel.
121128
// |label_| is the rtcDataChannel_.getLabel() result
122-
// |id| is the Freedom GUID for the underlying browser object.
123129
constructor(private rtcDataChannel_:freedom_RTCDataChannel.RTCDataChannel,
124-
private label_:string,
125-
id:string) {
130+
private label_:string) {
126131
this.dataFromPeerQueue = new handler.Queue<Data,void>();
127132
this.toPeerDataQueue_ = new handler.Queue<Data,void>();
128133
this.toPeerDataBytes_ = 0;
@@ -153,7 +158,7 @@ export class DataChannelClass implements DataChannel {
153158
});
154159
this.onceOpened.then(() => {
155160
this.opennedSuccessfully_ = true;
156-
this.toPeerDataQueue_.setNextHandler(this.handleSendDataToPeer_);
161+
this.conjestionControlSendHandler();
157162
});
158163
this.onceClosed.then(() => {
159164
if(!this.opennedSuccessfully_) {
@@ -164,6 +169,10 @@ export class DataChannelClass implements DataChannel {
164169
}
165170
this.opennedSuccessfully_ = false;
166171
});
172+
this.onceDrained_.then(() => {
173+
log.debug('all messages sent, closing');
174+
this.rtcDataChannel_.close();
175+
});
167176
}
168177

169178

@@ -303,6 +312,9 @@ export class DataChannelClass implements DataChannel {
303312
} else {
304313
if (this.toPeerDataQueue_.getLength() === 0) {
305314
this.setOverflow_(false);
315+
if (this.draining_) {
316+
this.fulfillDrained_();
317+
}
306318
}
307319
// This processes one block from the queue, which (in Chrome) is
308320
// expected to be no larger than 4 KB. We will then go through the
@@ -316,8 +328,17 @@ export class DataChannelClass implements DataChannel {
316328
});
317329
}
318330

319-
public close = () : void => {
320-
this.rtcDataChannel_.close();
331+
public close = () : Promise<void> => {
332+
log.debug('close requested (%1 messages to send)',
333+
this.toPeerDataQueue_.getLength());
334+
335+
this.draining_ = true;
336+
337+
if (this.toPeerDataQueue_.getLength() === 0) {
338+
this.fulfillDrained_();
339+
}
340+
341+
return this.onceClosed;
321342
}
322343

323344
public getBrowserBufferedAmount = () : Promise<number> => {
@@ -343,15 +364,19 @@ export class DataChannelClass implements DataChannel {
343364
}
344365
} // class DataChannelClass
345366

346-
347-
// Static, async constructor that returns a DataChannel ready for use.
348-
// |id| is the GUID generated by Freedom to identify the underlying
349-
// browser object.
367+
// Static constructor which constructs a core.rtcdatachannel instance
368+
// given a core.rtcdatachannel GUID.
350369
export function createFromFreedomId(id:string) : Promise<DataChannel> {
351-
var rtcDataChannel = freedom['core.rtcdatachannel'](id);
370+
return createFromRtcDataChannel(freedom['core.rtcdatachannel'](id));
371+
}
372+
373+
// Static constructor which constructs a core.rtcdatachannel instance
374+
// given a core.rtcdatachannel instance.
375+
export function createFromRtcDataChannel(
376+
rtcDataChannel:freedom_RTCDataChannel.RTCDataChannel) : Promise<DataChannel> {
352377
return rtcDataChannel.setBinaryType('arraybuffer').then(() => {
353378
return rtcDataChannel.getLabel().then((label:string) => {
354-
return new DataChannelClass(rtcDataChannel, label, id);
379+
return new DataChannelClass(rtcDataChannel, label);
355380
});
356381
});
357382
}

src/webrtc/peerconnection.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ freedom = freedomMocker.makeMockFreedomInModuleEnv({
1717
import PeerConnection = require('./peerconnection');
1818
import PeerConnectionClass = PeerConnection.PeerConnectionClass;
1919

20-
describe('WebRtc / PeerConnection', function() {
20+
describe('PeerConnection', function() {
2121
var mockRtcPeerConnection :MockFreedomRtcPeerConnection;
2222

2323
beforeEach(function() {

0 commit comments

Comments
 (0)