@@ -12,8 +12,6 @@ var url = require('url')
1212var common = require ( '../common' )
1313var Tracker = require ( './tracker' )
1414
15- var TIMEOUT = 15000
16-
1715inherits ( UDPTracker , Tracker )
1816
1917/**
@@ -29,6 +27,7 @@ function UDPTracker (client, announceUrl, opts) {
2927 debug ( 'new udp tracker %s' , announceUrl )
3028
3129 self . cleanupFns = [ ]
30+ self . maybeDestroyCleanup = null
3231}
3332
3433UDPTracker . prototype . DEFAULT_ANNOUNCE_INTERVAL = 30 * 60 * 1000 // 30 minutes
@@ -52,11 +51,31 @@ UDPTracker.prototype.destroy = function (cb) {
5251 self . destroyed = true
5352 clearInterval ( self . interval )
5453
55- self . cleanupFns . slice ( 0 ) . forEach ( function ( cleanup ) {
56- cleanup ( )
57- } )
58- self . cleanupFns = [ ]
59- cb ( null )
54+ // If there are no pending requests, destroy immediately.
55+ if ( self . cleanupFns . length === 0 ) return destroyCleanup ( )
56+
57+ // Otherwise, wait a short time for pending requests to complete, then force
58+ // destroy them.
59+ var timeout = setTimeout ( destroyCleanup , common . DESTROY_TIMEOUT )
60+
61+ // But, if all pending requests complete before the timeout fires, do cleanup
62+ // right away.
63+ self . maybeDestroyCleanup = function ( ) {
64+ if ( self . cleanupFns . length === 0 ) destroyCleanup ( )
65+ }
66+
67+ function destroyCleanup ( ) {
68+ if ( timeout ) {
69+ clearTimeout ( timeout )
70+ timeout = null
71+ }
72+ self . maybeDestroyCleanup = null
73+ self . cleanupFns . slice ( 0 ) . forEach ( function ( cleanup ) {
74+ cleanup ( )
75+ } )
76+ self . cleanupFns = [ ]
77+ cb ( null )
78+ }
6079}
6180
6281UDPTracker . prototype . _request = function ( opts ) {
@@ -66,41 +85,51 @@ UDPTracker.prototype._request = function (opts) {
6685 var transactionId = genTransactionId ( )
6786 var socket = dgram . createSocket ( 'udp4' )
6887
69- var cleanup = function ( ) {
70- if ( ! socket ) return
71- self . cleanupFns . splice ( self . cleanupFns . indexOf ( cleanup ) , 1 )
72- if ( timeout ) {
73- clearTimeout ( timeout )
74- timeout = null
75- }
76- socket . removeListener ( 'error' , onError )
77- socket . removeListener ( 'message' , onSocketMessage )
78- socket . on ( 'error' , noop ) // ignore all future errors
79- try { socket . close ( ) } catch ( err ) { }
80- socket = null
81- }
82- self . cleanupFns . push ( cleanup )
83-
84- // does not matter if `stopped` event arrives, so supress errors & cleanup after timeout
85- var ms = opts . event === 'stopped' ? TIMEOUT / 10 : TIMEOUT
8688 var timeout = setTimeout ( function ( ) {
87- timeout = null
89+ // does not matter if `stopped` event arrives, so supress errors
8890 if ( opts . event === 'stopped' ) cleanup ( )
8991 else onError ( new Error ( 'tracker request timed out (' + opts . event + ')' ) )
90- } , ms )
92+ timeout = null
93+ } , common . REQUEST_TIMEOUT )
9194 if ( timeout . unref ) timeout . unref ( )
9295
96+ self . cleanupFns . push ( cleanup )
97+
9398 send ( Buffer . concat ( [
9499 common . CONNECTION_ID ,
95100 common . toUInt32 ( common . ACTIONS . CONNECT ) ,
96101 transactionId
97102 ] ) )
98103
99- socket . on ( 'error' , onError )
104+ socket . once ( 'error' , onError )
100105 socket . on ( 'message' , onSocketMessage )
101106
102- function onSocketMessage ( msg ) {
107+ function cleanup ( ) {
108+ if ( timeout ) {
109+ clearTimeout ( timeout )
110+ timeout = null
111+ }
112+ if ( socket ) {
113+ self . cleanupFns . splice ( self . cleanupFns . indexOf ( cleanup ) , 1 )
114+ socket . removeListener ( 'error' , onError )
115+ socket . removeListener ( 'message' , onSocketMessage )
116+ socket . on ( 'error' , noop ) // ignore all future errors
117+ try { socket . close ( ) } catch ( err ) { }
118+ socket = null
119+ }
120+ if ( self . maybeDestroyCleanup ) self . maybeDestroyCleanup ( )
121+ }
122+
123+ function onError ( err ) {
124+ cleanup ( )
103125 if ( self . destroyed ) return
126+
127+ if ( err . message ) err . message += ' (' + self . announceUrl + ')'
128+ // errors will often happen if a tracker is offline, so don't treat it as fatal
129+ self . client . emit ( 'warning' , err )
130+ }
131+
132+ function onSocketMessage ( msg ) {
104133 if ( msg . length < 8 || msg . readUInt32BE ( 4 ) !== transactionId . readUInt32BE ( 0 ) ) {
105134 return onError ( new Error ( 'tracker sent invalid transaction id' ) )
106135 }
@@ -109,15 +138,20 @@ UDPTracker.prototype._request = function (opts) {
109138 debug ( 'UDP response %s, action %s' , self . announceUrl , action )
110139 switch ( action ) {
111140 case 0 : // handshake
141+ // Note: no check for `self.destroyed` so that pending messages to the
142+ // tracker can still be sent/received even after destroy() is called
143+
112144 if ( msg . length < 16 ) return onError ( new Error ( 'invalid udp handshake' ) )
113145
114146 if ( opts . _scrape ) scrape ( msg . slice ( 8 , 16 ) )
115147 else announce ( msg . slice ( 8 , 16 ) , opts )
116148
117- return
149+ break
118150
119151 case 1 : // announce
120152 cleanup ( )
153+ if ( self . destroyed ) return
154+
121155 if ( msg . length < 20 ) return onError ( new Error ( 'invalid announce message' ) )
122156
123157 var interval = msg . readUInt32BE ( 8 )
@@ -138,10 +172,13 @@ UDPTracker.prototype._request = function (opts) {
138172 addrs . forEach ( function ( addr ) {
139173 self . client . emit ( 'peer' , addr )
140174 } )
175+
141176 break
142177
143178 case 2 : // scrape
144179 cleanup ( )
180+ if ( self . destroyed ) return
181+
145182 if ( msg . length < 20 || ( msg . length - 8 ) % 12 !== 0 ) {
146183 return onError ( new Error ( 'invalid scrape message' ) )
147184 }
@@ -158,12 +195,16 @@ UDPTracker.prototype._request = function (opts) {
158195 incomplete : msg . readUInt32BE ( 16 + ( i * 12 ) )
159196 } )
160197 }
198+
161199 break
162200
163201 case 3 : // error
164202 cleanup ( )
203+ if ( self . destroyed ) return
204+
165205 if ( msg . length < 8 ) return onError ( new Error ( 'invalid error message' ) )
166206 self . client . emit ( 'warning' , new Error ( msg . slice ( 8 ) . toString ( ) ) )
207+
167208 break
168209
169210 default :
@@ -172,14 +213,6 @@ UDPTracker.prototype._request = function (opts) {
172213 }
173214 }
174215
175- function onError ( err ) {
176- if ( self . destroyed ) return
177- cleanup ( )
178- if ( err . message ) err . message += ' (' + self . announceUrl + ')'
179- // errors will often happen if a tracker is offline, so don't treat it as fatal
180- self . client . emit ( 'warning' , err )
181- }
182-
183216 function send ( message ) {
184217 if ( ! parsedUrl . port ) {
185218 parsedUrl . port = 80
0 commit comments