Skip to content

Commit

Permalink
Refactored a bunch of the client code. It behaves better now, but the…
Browse files Browse the repository at this point in the history
… tests run out of file handles.
  • Loading branch information
josephg committed Oct 8, 2011
1 parent 7b67736 commit 047664a
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 48 deletions.
16 changes: 8 additions & 8 deletions src/client/connection.coffee
Expand Up @@ -23,13 +23,14 @@ else
class Connection
constructor: (origin) ->
@docs = {}
@numDocs = 0

# Map of docName -> map of type -> function(data, error)
#
# Once socket.io isn't buggy, this will be rewritten to use socket.io's RPC.
@handlers = {}

@state = 'connecting'

# We can't reuse connections because the socket.io server doesn't
# emit connected events when a new connection comes in. Multiple documents
# are already multiplexed over the connection by socket.io anyway, so it
Expand All @@ -50,12 +51,13 @@ class Connection

# This avoids a bug in socket.io-client (v0.7.9) which causes
# subsequent connections on the same host to not fire a .connect event
if @socket.socket.connected
setTimeout (=> @connected()), 0
#if @socket.socket.connected
# setTimeout (=> @connected()), 0

disconnected: =>
# Start reconnect sequence
@emit 'disconnect'
@socket = null

connected: =>
# Stop reconnect sequence
Expand All @@ -67,7 +69,7 @@ class Connection
# The callback is optional. It takes (data, error). Data might be missing if the
# error was a connection error.
send: (msg, callback) ->
throw new Error 'Cannot send messages to a closed connection' if @socket == null
throw new Error "Cannot send message #{JSON.stringify msg} to a closed connection" if @socket == null

docName = msg.doc

Expand Down Expand Up @@ -131,11 +133,9 @@ class Connection
doc = new Doc(@, name, params.v, type, params.snapshot)
doc.created = !!params.create
@docs[name] = doc
@numDocs++

doc.on 'closed', =>
doc.on 'closing', =>
delete @docs[name]
@numDocs--

doc

Expand Down Expand Up @@ -200,7 +200,7 @@ class Connection

disconnect: () ->
if @socket
@emit 'disconnected'
@emit 'disconnecting'
@socket.disconnect()
@socket = null

Expand Down
2 changes: 2 additions & 0 deletions src/client/doc.coffee
Expand Up @@ -154,10 +154,12 @@ Doc = (connection, @name, @version, @type, @snapshot) ->
# Close a document.
# No unit tests for this so far.
@close = (callback) ->
return callback?() if connection.socket == null
connection.send {'doc':@name, open:false}, =>
callback() if callback
@emit 'closed'
return
@emit 'closing'

if @type.api
this[k] = v for k, v of @type.api
Expand Down
19 changes: 12 additions & 7 deletions src/client/index.coffee
Expand Up @@ -29,8 +29,11 @@ exports.open = do ->

unless connections[origin]
c = new Connection origin
c.on 'disconnected', -> delete connections[origin]
c.on 'connect failed', -> delete connections[origin]
c.numDocs = 0

del = -> delete connections[origin]
c.on 'disconnecting', del
c.on 'connect failed', del
connections[origin] = c

connections[origin]
Expand All @@ -41,23 +44,25 @@ exports.open = do ->
origin = null

c = getConnection origin
c.numDocs++
c.open docName, type, (doc, error) ->
if doc == null
c.numDocs--
c.disconnect() if c.numDocs == 0
callback null, error
else
# If you're using the bare API, connections are cleaned up as soon as there's no
# documents using them.
doc.on 'closed', ->
setTimeout ->
if c.numDocs == 0
c.disconnect()
, 0
doc.on 'closing', ->
c.numDocs--
if c.numDocs == 0
c.disconnect()

callback doc

c.on 'connect failed'


unless WEB?
exports.Doc = require('./doc').Doc
exports.Connection = require('./connection').Connection
Expand Down
47 changes: 42 additions & 5 deletions test/integration.coffee
Expand Up @@ -10,7 +10,16 @@ assert = require 'assert'
server = require '../src/server'
types = require '../src/types'

client = require '../src/client'
nativeclient = require '../src/client'
webclient = require './helpers/webclient'

{randomReal} = require './helpers'

nowOrLater = (fn) ->
if randomReal() < 0.5
fn()
else
process.nextTick fn

# Open the same document from 2 connections.
# So intense.
Expand All @@ -27,19 +36,22 @@ doubleOpen = (c1, c2, docName, type, callback) ->
assert.ok doc2
callback doc1, doc2 if doc1 && doc2

module.exports = testCase
genTests = (client) -> testCase
setUp: (callback) ->
# This is needed for types.text.generateRandomOp
require './types/text'

@name = 'testingdoc'
@server = server {db: {type: 'memory'}}
@server.listen =>
port = @server.address().port
@port = @server.address().port

@c1 = new client.Connection "http://localhost:#{port}/sjs"
# We use 127.0.0.1 here so if the process runs out of file handles,
# we'll get the correct error message instead of a generic DNS connection
# error.
@c1 = new client.Connection "http://127.0.0.1:#{@port}/sjs"
@c1.on 'connect', =>
@c2 = new client.Connection "http://localhost:#{port}/sjs"
@c2 = new client.Connection "http://127.0.0.1:#{@port}/sjs"
@c2.on 'connect', =>
callback()

Expand Down Expand Up @@ -75,6 +87,29 @@ module.exports = testCase
test.deepEqual doc2.snapshot, {}
test.done()

'Closing and opening documents works': (test) ->
num = 0
nextDocName = -> "doc#{num++}"

host = "http://127.0.0.1:#{@port}/sjs"

more = ->
client.open nextDocName(), 'text', host, (doc, error) =>
nowOrLater ->
doc.close()

# This test consumes open file handles. If you want to set
# it higher, you need to run:
# % ulimit -n 8096
# to increase the OS limit. This happens because of a bug in
# socket.io.
if num < 30
nowOrLater more
else
test.done()

more()

'randomized op spam test': (test) ->
doubleOpen @c1, @c2, @name, 'text', (doc1, doc2) =>
opsRemaining = 500
Expand Down Expand Up @@ -121,3 +156,5 @@ module.exports = testCase

# TODO: Add a randomized tester which also randomly denies ops.

exports.native = genTests nativeclient
exports.webclient = genTests webclient
13 changes: 13 additions & 0 deletions test/socketio.coffee
@@ -1,4 +1,17 @@
# Tests for the server's socketio interface
#
# I've had the very occasional flakeyness with these tests:
#
# Error: EADDRINUSE, Address already in use
# at doConnect (net.js:549:5)
# at net.js:725:9
# at dns.js:192:30
# at Object.lookup (dns.js:190:11)
# at Socket.connect (net.js:712:20)
# at Object.createConnection (net.js:265:5)
#
# I don't know what is causing this. - Its happening when the socket.io client connects
# to the socket.io server.

testCase = require('nodeunit').testCase
assert = require 'assert'
Expand Down
2 changes: 1 addition & 1 deletion webclient/share.js

Large diffs are not rendered by default.

51 changes: 25 additions & 26 deletions webclient/share.uncompressed.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 047664a

Please sign in to comment.