2

The following code tests the received datas on a server sent by a client.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:unittest/unittest.dart';

main() {
  ServerSocket ss;

  setUp(() => ServerSocket.bind('localhost', 9876).then((e) => ss = e));

  tearDown(() => ss.close());

  test('test 1', () {
    final line = new Completer<String>();
    ss.listen((s) => s.map(UTF8.decode).listen((s) => line.complete(s)));
    Socket.connect(ss.address, ss.port)
        .then((s) {
          s.write('test');
          return s.close();
        })
        .then((_) => line.future)
        .then(expectAsync((s) => expect(s, equals('test'))));
  });
}

This test displays:

unittest-suite-wait-for-done
PASS: test 1

All 1 tests passed.
unittest-suite-success

However, the process doesn't stop.

  1. Why the process is still running even with the ss.close() and s.close()?
  2. How to find what makes the process stay alive? Does Observatory provide something to debug that ?
Alexandre Ardhuin
  • 71,959
  • 15
  • 151
  • 132

2 Answers2

4

Edit: See Alex's answer below. Socket.drain() FTW!

According to the observatory io page, the socket created by the server was still open for writing. I've updated the code below to call destroy() on both the server and client socket. destroy() closes a Socket in both directions.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:unittest/unittest.dart';

main() {
  ServerSocket ss;

  setUp(() => ServerSocket.bind('localhost', 9876).then((e) => ss = e));

  tearDown(() => ss.close());

  test('test 1', () {
    final line = new Completer<String>();
    ss.listen((s) {
      s.map(UTF8.decode).listen((t) {
        line.complete(t);
        s.destroy();
      });
    });
    Socket.connect(ss.address, ss.port)
        .then((s) {
          s.write('test');
          return s.flush().then((_) {
            s.destroy();
            return line.future;
          });
        })
        .then(expectAsync((s) => expect(s, equals('test'))));
  });
}
Greg Lowe
  • 15,430
  • 2
  • 30
  • 33
  • Thanks Greg. It was not really obvious that `close()` was only closing the writing stream (this behaviour of [`close()`](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:io.Socket#id_close) is only described on [`destroy()`](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:io.Socket#id_destroy) :-/ ). – Alexandre Ardhuin Jan 27 '15 at 07:26
  • 2
    It's tripped me up a few times as well. Perhaps it's worth filing a bug, either the documentation needs to be improved or the api made simpler. I also would have thought that calling close() once in the server and once in the client should have been enough, but it is not. Would be nice to get a better answer from someone familiar with the implementation details. – Greg Lowe Jan 27 '15 at 07:44
  • I agree. I expected that `close()` has the `destroy()` behaviour. From an api point of view I would like to have something like `close({bool writing: true, bool reading: true})` – Alexandre Ardhuin Jan 27 '15 at 08:00
  • I assume it's because Socket implements IOSink which has close(). So I'm not sure that would be possible. Perhaps post on the mailing list, and see if Anders can shed some light on this. Maybe there is a simpler way to do this, and I'm missing something. – Greg Lowe Jan 27 '15 at 08:04
  • I think using destroy() should be a last resort, and closing both ends of the socket should be enough. If both destroy() calls are replaced with close(), and an s.drain() is added after the s.write(), then the s.flush can also be removed.This is a better solution, because it does exactly what is needed for a connection to terminate and disappear: Both ends must be closed, and both ends must be listened to. – William Hesse Jan 27 '15 at 10:02
  • Thanks, could you please explain why s.flush().then((_) => s.close()) causes the socket to remain open. I don't understand this. – Greg Lowe Jan 27 '15 at 19:18
  • Thanks Greg and William. I added an answer with some comments. I keep on thinking that `drain()` should be called by `close()` because it's really not obvious you have to call 2 methods to close a Socket. – Alexandre Ardhuin Jan 27 '15 at 22:22
1

According to William comment on the answer of Greg drain() and close() should be prefered instead of destroy().

Here is the working version:

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:unittest/unittest.dart';

main() {
  ServerSocket ss;

  setUp(() => ServerSocket.bind('localhost', 9876).then((e) => ss = e));

  tearDown(() => ss.close());

  test('test 1', () {
    final line = new Completer<String>();
    ss.listen((s) => UTF8.decodeStream(s)
                         .then((value) => line.complete(value))
                         .then((_)=> s.close()));
    Socket.connect(ss.address, ss.port)
        .then((s) {
          s.write('test');
          return Future.wait([s.drain(), s.close()]);
        })
        .then((_) => line.future)
        .then(expectAsync((s) => expect(s, equals('test'))));
  });
}

Some comments on this:

  • on the server side I used UTF8.decodeStream(s).then(...) instead of s.map(UTF8.decode).listen(...) because the value could be splitted in several chunks. In this case the socket would be closed too earlier.
  • on the client side I used Future.wait([s.drain(), s.close()]) instead of chaining Futures. If I use s.drain().then((_) => s.close()) the process blocks because the server is waiting for the end of data stream (triggered by s.close() on client side) to close the Socket and thus trigger the completation of the s.drain() on client side.
Community
  • 1
  • 1
Alexandre Ardhuin
  • 71,959
  • 15
  • 151
  • 132