Skip to content

Commit a42ec32

Browse files
joshuaspenceepriestley
authored and
epriestley
committedMay 29, 2014
Modify the Aphlict client to use LocalConnection.
Summary: Ref T4324. Currently, an Aphlict client (with a corresponding connection to the Aphlict Server) is created for every tab that a user has open. This significantly affects the scalability of Aphlict as a service. Instead, we can use `LocalConnection` instances to coordinate the communication of multiple Aphlict clients to the server. Similar functionality existed prior to D2704, but was removed as the author was not able to get this functionality working as intended. It seems that the main issue with the initial attempt was the use of the `setTimeout` function, which seemed to be a blocking call which prevented messages from being received. I have instead used an event-based model using a `Timer` object. Roughly this works as follows: # The first instance will create an `AphlictClient` and an `AphlictMaster`. The `AphlictClient` will register itself with the `AphlictMaster` and will consequently be notified of incoming messages. # The `AphlictClient` is then responsible for pinging the `AphlictMaster` at regular intervals. If the client does not ping the master in a given period of time, the master will assume that the client is dead and will remove the client from the pool. # Similarly, the `AphlictMaster` is required to respond to pings with a "pong" response. The pong response lets the clients know that the `AphlictMaster` is still alive. If the clients do not receive a pong in a given period of time, then the clients will attempt to spawn a new master. Test Plan: I have tested this on our Phabricator install with a few tabs opened and inspecting the console output. I will upload a screencast of my test results. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T4324 Differential Revision: https://secure.phabricator.com/D9327
1 parent 7f2b641 commit a42ec32

File tree

5 files changed

+314
-105
lines changed

5 files changed

+314
-105
lines changed
 

‎support/aphlict/client/build_aphlict_client.sh

+3-10
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,13 @@ if [ -z "$MXMLC" ]; then
99
fi;
1010

1111
set -e
12-
set -x
1312

14-
# cp -R $ROOT/externals/vegas/src $BASEDIR/src/vegas
15-
16-
(cd $BASEDIR && $MXMLC \
17-
-output aphlict.swf \
13+
$MXMLC \
14+
-output=$ROOT/webroot/rsrc/swf/aphlict.swf \
1815
-default-background-color=0x444444 \
1916
-default-size=500,500 \
2017
-warnings=true \
2118
-debug=true \
2219
-source-path=$ROOT/externals/vegas/src \
2320
-static-link-runtime-shared-libraries=true \
24-
src/Aphlict.as)
25-
26-
mv $BASEDIR/aphlict.swf $ROOT/webroot/rsrc/swf/aphlict.swf
27-
28-
# -target-player=10.2.0 \
21+
$BASEDIR/src/AphlictClient.as

‎support/aphlict/client/src/Aphlict.as

+16-95
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,38 @@
11
package {
22

3-
import flash.net.*;
4-
import flash.utils.*;
5-
import flash.media.*;
6-
import flash.display.*;
7-
import flash.events.*;
3+
import flash.display.Sprite;
84
import flash.external.ExternalInterface;
5+
import flash.net.LocalConnection;
96

10-
import vegas.strings.JSON;
117

128
public class Aphlict extends Sprite {
139

14-
private var client:String;
10+
/**
11+
* A transport channel used to receive data.
12+
*/
13+
protected var recv:LocalConnection;
1514

16-
private var socket:Socket;
17-
private var readBuffer:ByteArray;
15+
/**
16+
* A transport channel used to send data.
17+
*/
18+
protected var send:LocalConnection;
1819

19-
private var remoteServer:String;
20-
private var remotePort:Number;
2120

2221
public function Aphlict() {
2322
super();
2423

25-
ExternalInterface.addCallback('connect', this.externalConnect);
26-
ExternalInterface.call(
27-
'JX.Stratcom.invoke',
28-
'aphlict-component-ready',
29-
null,
30-
{});
31-
}
32-
33-
public function externalConnect(server:String, port:Number):void {
34-
this.externalInvoke('connect');
35-
36-
this.remoteServer = server;
37-
this.remotePort = port;
38-
39-
this.connectToServer();
40-
}
41-
42-
43-
public function connectToServer():void {
44-
var socket:Socket = new Socket();
45-
46-
socket.addEventListener(Event.CONNECT, didConnectSocket);
47-
socket.addEventListener(Event.CLOSE, didCloseSocket);
48-
socket.addEventListener(ProgressEvent.SOCKET_DATA, didReceiveSocket);
49-
50-
socket.addEventListener(IOErrorEvent.IO_ERROR, didIOErrorSocket);
51-
socket.addEventListener(
52-
SecurityErrorEvent.SECURITY_ERROR,
53-
didSecurityErrorSocket);
54-
55-
socket.connect(this.remoteServer, this.remotePort);
56-
57-
this.readBuffer = new ByteArray();
58-
this.socket = socket;
59-
}
60-
61-
private function didConnectSocket(event:Event):void {
62-
this.externalInvoke('connected');
63-
}
64-
65-
private function didCloseSocket(event:Event):void {
66-
this.externalInvoke('close');
67-
}
68-
69-
private function didIOErrorSocket(event:IOErrorEvent):void {
70-
this.externalInvoke('error', event.text);
71-
}
72-
73-
private function didSecurityErrorSocket(event:SecurityErrorEvent):void {
74-
this.externalInvoke('error', event.text);
75-
}
76-
77-
private function didReceiveSocket(event:Event):void {
78-
var b:ByteArray = this.readBuffer;
79-
this.socket.readBytes(b, b.length);
80-
81-
do {
82-
b = this.readBuffer;
83-
b.position = 0;
84-
85-
if (b.length <= 8) {
86-
break;
87-
}
88-
89-
var msg_len:Number = parseInt(b.readUTFBytes(8), 10);
90-
if (b.length >= msg_len + 8) {
91-
var bytes:String = b.readUTFBytes(msg_len);
92-
var data:Object = vegas.strings.JSON.deserialize(bytes);
93-
var t:ByteArray = new ByteArray();
94-
t.writeBytes(b, msg_len + 8);
95-
this.readBuffer = t;
96-
97-
this.receiveMessage(data);
98-
} else {
99-
break;
100-
}
101-
} while (true);
102-
103-
}
24+
this.recv = new LocalConnection();
25+
this.recv.client = this;
10426

105-
public function receiveMessage(msg:Object):void {
106-
this.externalInvoke('receive', msg);
27+
this.send = new LocalConnection();
10728
}
10829

109-
public function externalInvoke(type:String, object:Object = null):void {
30+
protected function externalInvoke(type:String, object:Object = null):void {
11031
ExternalInterface.call('JX.Aphlict.didReceiveEvent', type, object);
11132
}
11233

113-
public function log(message:String):void {
114-
ExternalInterface.call('console.log', message);
34+
protected function log(message:String):void {
35+
this.externalInvoke('log', message);
11536
}
11637

11738
}
+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package {
2+
3+
import flash.events.TimerEvent;
4+
import flash.external.ExternalInterface;
5+
import flash.utils.Timer;
6+
7+
8+
public class AphlictClient extends Aphlict {
9+
10+
/**
11+
* The connection name for this client. This will be used for the
12+
* @{class:LocalConnection} object.
13+
*/
14+
private var client:String;
15+
16+
/**
17+
* The expiry timestamp for the @{class:AphlictMaster}. If this time is
18+
* elapsed then the master will be assumed to be dead and another
19+
* @{class:AphlictClient} will create a master.
20+
*/
21+
private var expiry:Number = 0;
22+
23+
/**
24+
* The interval at which to ping the @{class:AphlictMaster}.
25+
*/
26+
public static const INTERVAL:Number = 3000;
27+
28+
private var master:AphlictMaster;
29+
private var timer:Timer;
30+
31+
private var remoteServer:String;
32+
private var remotePort:Number;
33+
34+
35+
public function AphlictClient() {
36+
super();
37+
38+
ExternalInterface.addCallback('connect', this.externalConnect);
39+
ExternalInterface.call(
40+
'JX.Stratcom.invoke',
41+
'aphlict-component-ready',
42+
null,
43+
{});
44+
}
45+
46+
public function externalConnect(server:String, port:Number):void {
47+
this.externalInvoke('connect');
48+
49+
this.remoteServer = server;
50+
this.remotePort = port;
51+
52+
this.client = AphlictClient.generateClientId();
53+
this.recv.connect(this.client);
54+
55+
this.timer = new Timer(AphlictClient.INTERVAL);
56+
this.timer.addEventListener(TimerEvent.TIMER, this.keepalive);
57+
58+
this.connectToMaster();
59+
}
60+
61+
/**
62+
* Generate a unique identifier that will be used to communicate with the
63+
* @{class:AphlictMaster}.
64+
*/
65+
private static function generateClientId():String {
66+
return 'aphlict_client_' + Math.round(Math.random() * 100000);
67+
}
68+
69+
/**
70+
* Create a new connection to the @{class:AphlictMaster}.
71+
*
72+
* If there is no current @{class:AphlictMaster} instance, then a new master
73+
* will be created.
74+
*/
75+
private function connectToMaster():void {
76+
this.timer.stop();
77+
78+
// Try to become the master.
79+
try {
80+
this.log('Attempting to become the master...');
81+
this.master = new AphlictMaster(this.remoteServer, this.remotePort);
82+
this.log('I am the master.');
83+
} catch (x:Error) {
84+
// Couldn't become the master
85+
this.log('Cannot become the master... probably one already exists');
86+
}
87+
88+
this.send.send('aphlict_master', 'register', this.client);
89+
this.expiry = new Date().getTime() + (5 * AphlictClient.INTERVAL);
90+
this.log('Registered client ' + this.client);
91+
92+
this.timer.start();
93+
}
94+
95+
/**
96+
* Send a keepalive signal to the @{class:AphlictMaster}.
97+
*
98+
* If the connection to the master has expired (because the master has not
99+
* sent a heartbeat signal), then a new connection to master will be
100+
* created.
101+
*/
102+
private function keepalive(event:TimerEvent):void {
103+
if (new Date().getTime() > this.expiry) {
104+
this.connectToMaster();
105+
}
106+
107+
this.send.send('aphlict_master', 'ping', this.client);
108+
}
109+
110+
/**
111+
* This function is used to receive the heartbeat signal from the
112+
* @{class:AphlictMaster}.
113+
*/
114+
public function pong():void {
115+
this.expiry = new Date().getTime() + (2 * AphlictClient.INTERVAL);
116+
}
117+
118+
/**
119+
* Receive a message from the Aphlict Server, via the
120+
* @{class:AphlictMaster}.
121+
*/
122+
public function receiveMessage(msg:Object):void {
123+
this.log('Received message.');
124+
this.externalInvoke('receive', msg);
125+
}
126+
127+
}
128+
129+
}

0 commit comments

Comments
 (0)
Failed to load comments.