passenger 4.0.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (79) hide show
  1. data.tar.gz.asc +7 -7
  2. data/NEWS +27 -0
  3. data/bin/passenger-config +6 -3
  4. data/bin/passenger-install-apache2-module +2 -2
  5. data/bin/passenger-install-nginx-module +16 -2
  6. data/build/agents.rb +4 -0
  7. data/build/apache2.rb +1 -1
  8. data/build/cplusplus_support.rb +1 -1
  9. data/build/cxx_tests.rb +3 -0
  10. data/build/packaging.rb +51 -8
  11. data/build/ruby_extension.rb +1 -1
  12. data/doc/Packaging.txt.md +20 -7
  13. data/doc/Users guide Apache.html +1 -1
  14. data/doc/Users guide Apache.txt +1 -1
  15. data/doc/Users guide Nginx.html +5 -4
  16. data/doc/Users guide Nginx.txt +1 -1
  17. data/doc/users_guide_snippets/installation.txt +5 -3
  18. data/ext/apache2/Configuration.cpp +12 -0
  19. data/ext/apache2/Configuration.hpp +7 -4
  20. data/ext/apache2/Hooks.cpp +29 -19
  21. data/ext/common/AgentsStarter.cpp +85 -57
  22. data/ext/common/AgentsStarter.h +570 -42
  23. data/ext/common/ApplicationPool2/DirectSpawner.h +5 -2
  24. data/ext/common/ApplicationPool2/Implementation.cpp +7 -1
  25. data/ext/common/ApplicationPool2/Pool.h +6 -3
  26. data/ext/common/ApplicationPool2/Process.h +12 -3
  27. data/ext/common/ApplicationPool2/SmartSpawner.h +2 -1
  28. data/ext/common/Constants.h +4 -1
  29. data/ext/common/EventedBufferedInput.h +139 -16
  30. data/ext/common/MultiLibeio.cpp +4 -2
  31. data/ext/common/SafeLibev.h +15 -62
  32. data/ext/common/ServerInstanceDir.h +10 -26
  33. data/ext/common/Utils.cpp +1 -3
  34. data/ext/common/Utils.h +1 -1
  35. data/ext/common/Utils/StrIntUtils.cpp +9 -0
  36. data/ext/common/Utils/StrIntUtils.h +5 -0
  37. data/ext/common/Utils/VariantMap.h +63 -14
  38. data/ext/common/agents/Base.cpp +50 -15
  39. data/ext/common/agents/HelperAgent/AgentOptions.h +20 -12
  40. data/ext/common/agents/HelperAgent/FileBackedPipe.h +1 -1
  41. data/ext/common/agents/HelperAgent/Main.cpp +5 -4
  42. data/ext/common/agents/HelperAgent/RequestHandler.h +1 -1
  43. data/ext/common/agents/LoggingAgent/Main.cpp +0 -1
  44. data/ext/common/agents/LoggingAgent/RemoteSender.h +2 -2
  45. data/ext/common/agents/SpawnPreparer.cpp +23 -5
  46. data/ext/common/agents/Watchdog/AgentWatcher.cpp +508 -0
  47. data/ext/common/agents/Watchdog/HelperAgentWatcher.cpp +93 -0
  48. data/ext/common/agents/Watchdog/LoggingAgentWatcher.cpp +68 -0
  49. data/ext/common/agents/Watchdog/Main.cpp +180 -802
  50. data/ext/common/agents/Watchdog/ServerInstanceDirToucher.cpp +111 -0
  51. data/ext/nginx/Configuration.c +107 -92
  52. data/ext/nginx/Configuration.h +1 -0
  53. data/ext/nginx/ContentHandler.c +6 -6
  54. data/ext/nginx/ContentHandler.h +1 -1
  55. data/ext/nginx/config +8 -2
  56. data/ext/nginx/ngx_http_passenger_module.c +54 -60
  57. data/ext/nginx/ngx_http_passenger_module.h +6 -6
  58. data/lib/phusion_passenger.rb +17 -10
  59. data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
  60. data/lib/phusion_passenger/common_library.rb +0 -1
  61. data/lib/phusion_passenger/platform_info.rb +10 -1
  62. data/lib/phusion_passenger/platform_info/depcheck.rb +4 -4
  63. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +2 -2
  64. data/lib/phusion_passenger/platform_info/ruby.rb +7 -0
  65. data/lib/phusion_passenger/request_handler.rb +119 -42
  66. data/lib/phusion_passenger/request_handler/thread_handler.rb +25 -22
  67. data/lib/phusion_passenger/standalone/command.rb +2 -0
  68. data/lib/phusion_passenger/standalone/runtime_installer.rb +4 -3
  69. data/lib/phusion_passenger/standalone/start_command.rb +49 -37
  70. data/resources/templates/nginx/pcre_checksum_could_not_be_verified.txt.erb +11 -0
  71. data/test/cxx/CxxTestMain.cpp +2 -0
  72. data/test/cxx/EventedBufferedInputTest.cpp +758 -0
  73. data/test/cxx/ServerInstanceDirTest.cpp +16 -31
  74. data/test/cxx/TestSupport.cpp +2 -1
  75. data/test/cxx/VariantMapTest.cpp +23 -11
  76. metadata +8 -4
  77. metadata.gz.asc +7 -7
  78. data/ext/common/AgentsStarter.hpp +0 -655
  79. data/lib/phusion_passenger/utils/robust_interruption.rb +0 -173
@@ -109,7 +109,9 @@ private:
109
109
  startBackgroundThread(detachProcessMain, (void *) (long) pid);
110
110
  }
111
111
 
112
- vector<string> createCommand(const Options &options, shared_array<const char *> &args) const {
112
+ vector<string> createCommand(const Options &options, const SpawnPreparationInfo &preparation,
113
+ shared_array<const char *> &args) const
114
+ {
113
115
  vector<string> startCommandArgs;
114
116
  string agentsDir = resourceLocator.getAgentsDir();
115
117
  vector<string> command;
@@ -129,6 +131,7 @@ private:
129
131
  command.push_back(agentsDir + "/SpawnPreparer");
130
132
  }
131
133
  command.push_back(agentsDir + "/SpawnPreparer");
134
+ command.push_back(preparation.appRoot);
132
135
  command.push_back(serializeEnvvarsFromPoolOptions(options));
133
136
  command.push_back(startCommandArgs[0]);
134
137
  // Note: do not try to set a process title here.
@@ -166,8 +169,8 @@ public:
166
169
  possiblyRaiseInternalError(options);
167
170
 
168
171
  shared_array<const char *> args;
169
- vector<string> command = createCommand(options, args);
170
172
  SpawnPreparationInfo preparation = prepareSpawn(options);
173
+ vector<string> command = createCommand(options, preparation, args);
171
174
  SocketPair adminSocket = createUnixSocketPair();
172
175
  Pipe errorPipe = createPipe();
173
176
  DebugDirPtr debugDir = make_shared<DebugDir>(preparation.uid, preparation.gid);
@@ -953,6 +953,12 @@ Group::detachedProcessesCheckerMain(GroupPtr self) {
953
953
  assert(process->getLifeStatus() == Process::DEAD);
954
954
  it++;
955
955
  removeProcessFromList(process, detachedProcesses);
956
+ } else if (process->shutdownTimeoutExpired()) {
957
+ P_WARN("Detached process " << process->inspect() <<
958
+ " didn't shut down within " PROCESS_SHUTDOWN_TIMEOUT_DISPLAY
959
+ ". Forcefully killing it with SIGKILL.");
960
+ kill(process->pid, SIGKILL);
961
+ it++;
956
962
  } else {
957
963
  it++;
958
964
  }
@@ -991,7 +997,7 @@ Group::detachedProcessesCheckerMain(GroupPtr self) {
991
997
  // someone wakes us up.
992
998
  UPDATE_TRACE_POINT();
993
999
  detachedProcessesCheckerCond.timed_wait(lock,
994
- posix_time::milliseconds(10));
1000
+ posix_time::milliseconds(100));
995
1001
  }
996
1002
  }
997
1003
 
@@ -434,17 +434,20 @@ public:
434
434
  for (p_it = processes.begin(); p_it != processes.end(); p_it++) {
435
435
  const ProcessPtr &process = *p_it;
436
436
  char buf[128];
437
+ char cpubuf[10];
437
438
  char membuf[10];
438
439
 
440
+ snprintf(cpubuf, sizeof(cpubuf), "%d%%", (int) process->metrics.cpu);
439
441
  snprintf(membuf, sizeof(membuf), "%ldM",
440
442
  (unsigned long) (process->metrics.realMemory() / 1024));
441
443
  snprintf(buf, sizeof(buf),
442
- " * PID : %-5lu Sessions : %-2u Processed: %-5u Uptime: %s\n"
443
- " Memory: %-5s Last used: %s ago",
444
+ " * PID: %-5lu Sessions: %-2u Processed: %-5u Uptime: %s\n"
445
+ " CPU: %-5s Memory : %-5s Last used: %s ago",
444
446
  (unsigned long) process->pid,
445
447
  process->sessions,
446
448
  process->processed,
447
449
  process->uptime().c_str(),
450
+ cpubuf,
448
451
  membuf,
449
452
  distanceOfTimeInWords(process->lastUsed / 1000000).c_str());
450
453
  result << buf << endl;
@@ -454,7 +457,7 @@ public:
454
457
  } else if (process->enabled == Process::DISABLED) {
455
458
  result << " DISABLED" << endl;
456
459
  } else if (process->enabled == Process::DETACHED) {
457
- result << " Shutting down...";
460
+ result << " Shutting down..." << endl;
458
461
  }
459
462
 
460
463
  const Socket *socket;
@@ -39,6 +39,7 @@
39
39
  #include <ApplicationPool2/Socket.h>
40
40
  #include <ApplicationPool2/Session.h>
41
41
  #include <ApplicationPool2/PipeWatcher.h>
42
+ #include <Constants.h>
42
43
  #include <FileDescriptor.h>
43
44
  #include <SafeLibev.h>
44
45
  #include <Logging.h>
@@ -233,10 +234,10 @@ public:
233
234
  /** This process has been detached, and the detached processes checker has
234
235
  * verified that there are no active sessions left and has told the process
235
236
  * to shut down. In this state we're supposed to wait until the process
236
- * has actually shutdown, after which clean() must be called. */
237
+ * has actually shutdown, after which cleanup() must be called. */
237
238
  SHUTDOWN_TRIGGERED,
238
239
  /**
239
- * The process has exited and clean() has been called. In this state,
240
+ * The process has exited and cleanup() has been called. In this state,
240
241
  * this object is no longer usable.
241
242
  */
242
243
  DEAD
@@ -275,6 +276,8 @@ public:
275
276
  } oobwStatus;
276
277
  /** Caches whether or not the OS process still exists. */
277
278
  mutable bool m_osProcessExists;
279
+ /** Time at which shutdown began. */
280
+ time_t shutdownStartTime;
278
281
  /** Collected by Pool::collectAnalytics(). */
279
282
  ProcessMetrics metrics;
280
283
 
@@ -309,7 +312,8 @@ public:
309
312
  lifeStatus(ALIVE),
310
313
  enabled(ENABLED),
311
314
  oobwStatus(OOBW_NOT_ACTIVE),
312
- m_osProcessExists(true)
315
+ m_osProcessExists(true),
316
+ shutdownStartTime(0)
313
317
  {
314
318
  SpawnerConfigPtr config;
315
319
  if (_config == NULL) {
@@ -412,12 +416,17 @@ public:
412
416
  lock_guard<boost::mutex> lock(lifetimeSyncher);
413
417
  assert(lifeStatus == ALIVE);
414
418
  lifeStatus = SHUTDOWN_TRIGGERED;
419
+ shutdownStartTime = SystemTime::get();
415
420
  }
416
421
  if (!dummy) {
417
422
  syscalls::shutdown(adminSocket, SHUT_WR);
418
423
  }
419
424
  }
420
425
 
426
+ bool shutdownTimeoutExpired() const {
427
+ return SystemTime::get() >= shutdownStartTime + PROCESS_SHUTDOWN_TIMEOUT;
428
+ }
429
+
421
430
  bool canCleanup() const {
422
431
  return getLifeStatus() == SHUTDOWN_TRIGGERED && !osProcessExists();
423
432
  }
@@ -120,6 +120,7 @@ private:
120
120
  command.push_back(agentsDir + "/SpawnPreparer");
121
121
  }
122
122
  command.push_back(agentsDir + "/SpawnPreparer");
123
+ command.push_back(preparation.appRoot);
123
124
  command.push_back(serializeEnvvarsFromPoolOptions(options));
124
125
  command.push_back(preloaderCommand[0]);
125
126
  // Note: do not try to set a process title here.
@@ -210,8 +211,8 @@ private:
210
211
  checkChrootDirectories(options);
211
212
 
212
213
  shared_array<const char *> args;
213
- vector<string> command = createRealPreloaderCommand(options, args);
214
214
  preparation = prepareSpawn(options);
215
+ vector<string> command = createRealPreloaderCommand(options, args);
215
216
  SocketPair adminSocket = createUnixSocketPair();
216
217
  Pipe errorPipe = createPipe();
217
218
  DebugDirPtr debugDir = make_shared<DebugDir>(preparation.uid, preparation.gid);
@@ -26,7 +26,7 @@
26
26
  #define _PASSENGER_CONSTANTS_H_
27
27
 
28
28
  /* Don't forget to update lib/phusion_passenger.rb too. */
29
- #define PASSENGER_VERSION "4.0.2"
29
+ #define PASSENGER_VERSION "4.0.3"
30
30
 
31
31
  #define FEEDBACK_FD 3
32
32
 
@@ -49,4 +49,7 @@
49
49
 
50
50
  #define POOL_HELPER_THREAD_STACK_SIZE (1024 * 256)
51
51
 
52
+ #define PROCESS_SHUTDOWN_TIMEOUT 60 /* seconds */
53
+ #define PROCESS_SHUTDOWN_TIMEOUT_DISPLAY "1 minute"
54
+
52
55
  #endif /* _PASSENGER_CONSTANTS_H */
@@ -60,16 +60,23 @@ using namespace oxt;
60
60
  * the number of bytes that it has actually consumed. If not everything has been
61
61
  * consumed, then the handler will be called with the remaining data in the next
62
62
  * tick.
63
- *
64
- * TODO: this code is directly ported from Zangetsu's socket_input_wrapper.js. We
65
- * should port over the unit tests too.
66
63
  */
67
64
  template<size_t bufferSize = 1024 * 8>
68
65
  class EventedBufferedInput: public enable_shared_from_this< EventedBufferedInput<bufferSize> > {
69
66
  private:
70
67
  enum State {
71
68
  LIVE,
69
+ /**
70
+ * @invariant
71
+ * paused
72
+ * socketPaused
73
+ */
72
74
  END_OF_STREAM,
75
+ /**
76
+ * @invariant
77
+ * paused
78
+ * socketPaused
79
+ */
73
80
  READ_ERROR,
74
81
  CLOSED
75
82
  };
@@ -78,13 +85,54 @@ private:
78
85
  FileDescriptor fd;
79
86
  ev::io watcher;
80
87
  StaticString buffer;
81
- State state;
82
- bool paused;
83
- bool socketPaused;
84
- bool nextTickInstalled;
88
+
89
+ State state: 2;
90
+ /**
91
+ * Whether this EventedBufferedInput is paused (not started). If it's
92
+ * paused it should not emit data events.
93
+ *
94
+ * @invariant
95
+ * if paused:
96
+ * socketPaused
97
+ */
98
+ bool paused: 1;
99
+ /**
100
+ * Whether the underlying socket is also paused. This does not
101
+ * necessarily mean the EventedBufferedInput is also paused because
102
+ * it may be emitting data events from its internal buffer.
103
+ */
104
+ bool socketPaused: 1;
105
+ /**
106
+ * Whether the code is inside a processingBuffer() call.
107
+ */
108
+ bool processingBuffer: 1;
109
+ /**
110
+ * Whether processBuffer() is scheduled to be called in the next
111
+ * event loop iteration.
112
+ */
113
+ bool nextTickInstalled: 1;
114
+ /**
115
+ * Increment this number to ensure that previously scheduled
116
+ * processBuffer() calls will do nothing, effectively canceling
117
+ * its scheduled calls.
118
+ */
119
+ unsigned int generation;
85
120
  int error;
121
+
86
122
  char bufferData[bufferSize];
87
123
 
124
+ void verifyInvariants() {
125
+ // !a || b: logical equivalent of a IMPLIES b.
126
+
127
+ assert(!( state == END_OF_STREAM ) || ( paused ));
128
+ assert(!( state == END_OF_STREAM ) || ( socketPaused ));
129
+
130
+ assert(!( state == READ_ERROR ) || ( paused ));
131
+ assert(!( state == READ_ERROR ) || ( socketPaused ));
132
+
133
+ assert(!( paused ) || ( socketPaused ));
134
+ }
135
+
88
136
  void resetCallbackFields() {
89
137
  onData = NULL;
90
138
  onError = NULL;
@@ -96,7 +144,10 @@ private:
96
144
  shared_ptr< EventedBufferedInput<bufferSize> > self = EventedBufferedInput<bufferSize>::shared_from_this();
97
145
 
98
146
  EBI_TRACE("onReadable");
99
- ssize_t ret = syscalls::read(fd, bufferData, bufferSize);
147
+ verifyInvariants();
148
+ assert(!nextTickInstalled);
149
+
150
+ ssize_t ret = readSocket(bufferData, bufferSize);
100
151
  if (ret == -1) {
101
152
  if (errno != EAGAIN) {
102
153
  error = errno;
@@ -109,8 +160,11 @@ private:
109
160
  watcher.stop();
110
161
  state = READ_ERROR;
111
162
  paused = true;
163
+ socketPaused = true;
164
+ verifyInvariants();
112
165
  if (onError != NULL) {
113
166
  onError(self, "Cannot read from socket", error);
167
+ verifyInvariants();
114
168
  }
115
169
  }
116
170
 
@@ -124,7 +178,10 @@ private:
124
178
  watcher.stop();
125
179
  state = END_OF_STREAM;
126
180
  paused = true;
181
+ socketPaused = true;
182
+ verifyInvariants();
127
183
  onData(self, StaticString());
184
+ verifyInvariants();
128
185
 
129
186
  } else {
130
187
  EBI_TRACE("read " << ret << " bytes");
@@ -135,29 +192,51 @@ private:
135
192
 
136
193
  buffer = StaticString(bufferData, ret);
137
194
  processBuffer();
195
+ verifyInvariants();
138
196
  }
139
197
  }
140
198
 
141
199
  void processBufferInNextTick() {
142
200
  if (!nextTickInstalled) {
143
201
  nextTickInstalled = true;
144
- libev->runAsync(boost::bind(
202
+ libev->runLater(boost::bind(
145
203
  realProcessBufferInNextTick,
146
- weak_ptr< EventedBufferedInput<bufferSize> >(this->shared_from_this())
204
+ weak_ptr< EventedBufferedInput<bufferSize> >(this->shared_from_this()),
205
+ generation
147
206
  ));
148
207
  }
149
208
  }
150
209
 
151
- static void realProcessBufferInNextTick(weak_ptr< EventedBufferedInput<bufferSize> > wself) {
210
+ static void realProcessBufferInNextTick(weak_ptr< EventedBufferedInput<bufferSize> > wself,
211
+ unsigned int generation)
212
+ {
152
213
  shared_ptr< EventedBufferedInput<bufferSize> > self = wself.lock();
153
- if (self != NULL) {
214
+ if (self != NULL && generation == self->generation) {
215
+ self->verifyInvariants();
154
216
  self->nextTickInstalled = false;
155
217
  self->processBuffer();
218
+ self->verifyInvariants();
156
219
  }
157
220
  }
158
221
 
222
+ struct SetProcessingBufferToFalse {
223
+ EventedBufferedInput<bufferSize> *self;
224
+
225
+ SetProcessingBufferToFalse(EventedBufferedInput<bufferSize> *_self) {
226
+ self = _self;
227
+ }
228
+
229
+ ~SetProcessingBufferToFalse() {
230
+ self->processingBuffer = false;
231
+ }
232
+ };
233
+
159
234
  void processBuffer() {
160
235
  EBI_TRACE("processBuffer");
236
+ assert(!processingBuffer);
237
+ processingBuffer = true;
238
+ SetProcessingBufferToFalse flagGuard(this);
239
+
161
240
  if (state == CLOSED) {
162
241
  return;
163
242
  }
@@ -179,6 +258,7 @@ private:
179
258
  socketPaused = false;
180
259
  watcher.start();
181
260
  }
261
+ cancelScheduledProcessBufferCall();
182
262
  } else {
183
263
  buffer = buffer.substr(consumed);
184
264
  if (!socketPaused) {
@@ -189,18 +269,36 @@ private:
189
269
  // Consume rest of the data in the next tick.
190
270
  EBI_TRACE("Consume rest in next tick");
191
271
  processBufferInNextTick();
272
+ } else {
273
+ cancelScheduledProcessBufferCall();
192
274
  }
193
275
  }
276
+
277
+ afterProcessingBuffer();
194
278
  }
195
279
 
196
- void _reset(SafeLibev *libev, const FileDescriptor &fd) {
280
+ void cancelScheduledProcessBufferCall() {
281
+ if (nextTickInstalled) {
282
+ nextTickInstalled = false;
283
+ generation++;
284
+ }
285
+ }
286
+
287
+ void _reset(SafeLibev *libev, const FileDescriptor &fd, bool firstTime = false) {
288
+ if (firstTime) {
289
+ generation = 0;
290
+ } else {
291
+ verifyInvariants();
292
+ }
197
293
  this->libev = libev;
198
294
  this->fd = fd;
199
295
  buffer = StaticString();
200
296
  state = LIVE;
201
297
  paused = true;
202
298
  socketPaused = true;
299
+ processingBuffer = false;
203
300
  nextTickInstalled = false;
301
+ generation++;
204
302
  error = 0;
205
303
  if (watcher.is_active()) {
206
304
  watcher.stop();
@@ -211,6 +309,16 @@ private:
211
309
  if (fd != -1) {
212
310
  watcher.set(fd, ev::READ);
213
311
  }
312
+ verifyInvariants();
313
+ }
314
+
315
+ protected:
316
+ virtual ssize_t readSocket(void *buf, size_t n) {
317
+ return syscalls::read(fd, buf, n);
318
+ }
319
+
320
+ virtual void afterProcessingBuffer() {
321
+ // Do nothing. To be overridden in unit tests.
214
322
  }
215
323
 
216
324
  public:
@@ -223,21 +331,24 @@ public:
223
331
 
224
332
  EventedBufferedInput() {
225
333
  resetCallbackFields();
226
- _reset(NULL, FileDescriptor());
334
+ _reset(NULL, FileDescriptor(), true);
227
335
  watcher.set<EventedBufferedInput<bufferSize>,
228
336
  &EventedBufferedInput<bufferSize>::onReadable>(this);
229
337
  EBI_TRACE("created");
338
+ verifyInvariants();
230
339
  }
231
340
 
232
341
  EventedBufferedInput(SafeLibev *libev, const FileDescriptor &fd) {
233
342
  resetCallbackFields();
234
- _reset(libev, fd);
343
+ _reset(libev, fd, true);
235
344
  watcher.set<EventedBufferedInput<bufferSize>,
236
345
  &EventedBufferedInput<bufferSize>::onReadable>(this);
237
346
  EBI_TRACE("created");
347
+ verifyInvariants();
238
348
  }
239
349
 
240
- ~EventedBufferedInput() {
350
+ virtual ~EventedBufferedInput() {
351
+ cancelScheduledProcessBufferCall();
241
352
  watcher.stop();
242
353
  EBI_TRACE("destroyed");
243
354
  }
@@ -254,17 +365,21 @@ public:
254
365
  void stop() {
255
366
  if (state == LIVE && !paused) {
256
367
  EBI_TRACE("stop()");
368
+ verifyInvariants();
257
369
  paused = true;
258
370
  if (!socketPaused) {
259
371
  socketPaused = true;
260
372
  watcher.stop();
261
373
  }
374
+ cancelScheduledProcessBufferCall();
375
+ verifyInvariants();
262
376
  }
263
377
  }
264
378
 
265
379
  void start() {
266
380
  if (state == LIVE && paused) {
267
381
  EBI_TRACE("start()");
382
+ verifyInvariants();
268
383
  assert(socketPaused);
269
384
 
270
385
  paused = false;
@@ -273,7 +388,9 @@ public:
273
388
  } else {
274
389
  socketPaused = false;
275
390
  watcher.start();
391
+ cancelScheduledProcessBufferCall();
276
392
  }
393
+ verifyInvariants();
277
394
  }
278
395
  }
279
396
 
@@ -281,11 +398,16 @@ public:
281
398
  return !paused;
282
399
  }
283
400
 
401
+ bool isSocketStarted() const {
402
+ return !socketPaused;
403
+ }
404
+
284
405
  bool endReached() const {
285
406
  return state == END_OF_STREAM;
286
407
  }
287
408
 
288
409
  void readNow() {
410
+ assert(!nextTickInstalled);
289
411
  onReadable(watcher, 0);
290
412
  }
291
413
 
@@ -322,6 +444,7 @@ public:
322
444
  result << ", paused=" << paused;
323
445
  result << ", socketPaused=" << socketPaused;
324
446
  result << ", nextTickInstalled=" << nextTickInstalled;
447
+ result << ", generation=" << generation;
325
448
  result << ", error=" << error;
326
449
 
327
450
  return result.str();