Jump to page: 1 2 3
Thread overview
NIO+Multithreaded TCPSocket listener, very low cpu utilisation
Nov 14, 2017
ade90036
Nov 14, 2017
kdevel
Nov 14, 2017
kdevel
Nov 15, 2017
ade90036
Nov 15, 2017
ade90036
Nov 15, 2017
Daniel Kozak
Nov 15, 2017
Daniel Kozak
Nov 15, 2017
kdevel
Nov 16, 2017
ade90036
Nov 16, 2017
Daniel Kozak
Nov 16, 2017
ade90036
Nov 16, 2017
Daniel Kozak
Nov 16, 2017
ade90036
Nov 16, 2017
ade90036
Nov 16, 2017
ade90036
Nov 16, 2017
ade90036
Nov 16, 2017
Daniel Kozak
Nov 17, 2017
kdevel
Nov 17, 2017
kdevel
Nov 15, 2017
Daniel Kozak
Nov 15, 2017
kdevel
November 14, 2017
Hi Forum,

Let's cut the chase, i'm a newby in Dlang. I have 15+ years experience in java and 7+ years experience in C++.

I found D very fascinating and the sugar coated syntax very appealing to my style of coding. (groovy like)

I've been trying to learn Dland and  bring it thought the motions by creating a very simple and basic TCPSocket listerner that when you send a request it responds with an HTTP response over a specific port. (localhost:4445)

I have the code working on a worked thread and when the socket accept() it defers the processing of the request (socket) in a different thread backed by TaskPool(8).

i have 8 logical core, which is a macBook pro retina 16gb, i7.

What i'm expecting to see is the CPU of my 8 core I7 go through the roof and nearly melt (hope not) but at-least have the fan on at sustainable level and obtain full CPU utilisation.

What i'm benchmarking it against is a JAVA NIO2 implementation. This implementation achieves very high CPU utilisation and high throughput. The process utilisation averages 400% at times and the fan is really searching for cold air. (Niceeee)

However, when i run the Dlang program i see i very poor CPU utilisation. The fan is always in silent mode.

Not sure if you are familiar with MacOS cpu metrics, but they are based per core. So dland program reports 100% under the process monitor (which equates to one core) and the overall system CPU utilisation is 13%.

I would have expected to see a much higher cpu utilisation but it is not happening. I have been trying different variation of the same implementation but not luck.

I'm starting to suspect that this is an BUG related to the macOS but i would like to confirm or atleast have a second pair of eyes having a look.

```Code

import std.algorithm : remove;
import std.conv : to;
import core.thread: Thread;
import std.socket : InternetAddress, Socket, SocketException, SocketSet, TcpSocket, SocketShutdown;
import core.time : Duration, dur;
import std.stdio : writeln, writefln;
import std.parallelism : task, TaskPool;

void main(string[] args)
{
    ushort port;

    if (args.length >= 2)
        port = to!ushort(args[1]);
    else
        port = 4447;

    auto listener = new TcpSocket();
    assert(listener.isAlive);
    listener.blocking = false;
    listener.bind(new InternetAddress(port));
    listener.listen(100);
    writefln("Listening on port %d.", port);

    auto taskPool = new TaskPool(8);

    new Thread({
        auto listeningSet = new SocketSet();
        while(true) {
            listeningSet.add(listener);
            if (Socket.select(listeningSet, null, null, dur!"nsecs"(150)) > 0) {
                if (listeningSet.isSet(listener))        // connection request
                {
                    Socket socket = null;
                    scope (failure)
                    {
                        writefln("Error accepting");

                        if (socket)
                            socket.close();
                    }
                    socket = listener.accept();
                    assert(socket.isAlive);
                    assert(listener.isAlive);
                    //writefln("Connection from %s established.", socket.remoteAddress().toString());
                    auto task = task!handle_socket(socket);
                    taskPool.put(task);
                }
            }
            listeningSet.reset();
        }
    }).start();
}

void handle_socket(Socket socket) {
    auto socketSet = new SocketSet();
    while(true) {
        socketSet.add(socket);
        if (Socket.select(socketSet, null, null, dur!"nsecs"(150)) > 0) {
            char[1024] buf;
            auto datLength = socket.receive(buf[]);

            if (datLength == Socket.ERROR)
                writeln("Connection error.");
            else if (datLength != 0)
            {
                //writefln("Received %d bytes from %s: \"%s\"", datLength, socket.remoteAddress().toString(), buf[0..datLength]);
                //writefln("Writing response");

                socket.send("HTTP/1.1 200 OK
                                               Server: dland:v2.076.1
                                               Date: Tue, 11 Nov 2017 15:56:02 GMT
                                               Content-Type: text/plain; charset=UTF-8
                                               Content-Length: 32

                                               <html><head></head><body>Hello World!</body></html>");
            }
            // release socket resources now
            socket.shutdown(SocketShutdown.BOTH);

            socket.close();

            break;

        }
        socketSet.reset();
    }
}

```

You help in understanding this matter is extremelly helpfull.

Regards








November 14, 2017
On Tuesday, 14 November 2017 at 19:57:54 UTC, ade90036 wrote:
>                 socket.send("HTTP/1.1 200 OK
>                                                Server: dland:v2.076.1
>                                                Date: Tue, 11 Nov 2017 15:56:02 GMT
>                                                Content-Type: text/plain; charset=UTF-8
>                                                Content-Length: 32
>
>                                                <html><head></head><body>Hello World!</body></html>");
>             }

Some cosmetic changes: It is possible, that your HTTP client gets confused by the data sent?

```
                socket.send("HTTP/1.1 200 OK
Server: dland:v2.076.1
Date: Tue, 11 Nov 2017 15:56:02 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 51

<html><head></head><body>Hello World!</body></html>".to_retlf);

string to_retlf (string s)
{
   import std.algorithm;
   import std.string;
   return s
      .lineSplitter
      .map!(a => chomp (a))
      .join ("\r\n");
}
```

The Content-Length given is too short. The Content-Type also was wrong.
November 14, 2017
On Tuesday, 14 November 2017 at 19:57:54 UTC, ade90036 wrote:

>         while(true) {
>             listeningSet.add(listener);
>             if (Socket.select(listeningSet, null, null, dur!"nsecs"(150)) > 0) {

Why do you ever timeout? This loop consumes 100 % (a single core) when idle on my machine.


November 15, 2017
On Tuesday, 14 November 2017 at 21:09:40 UTC, kdevel wrote:
> On Tuesday, 14 November 2017 at 19:57:54 UTC, ade90036 wrote:
>
>>         while(true) {
>>             listeningSet.add(listener);
>>             if (Socket.select(listeningSet, null, null, dur!"nsecs"(150)) > 0) {
>
> Why do you ever timeout? This loop consumes 100 % (a single core) when idle on my machine.

Thanks for you feedback, i'll make those cosmetic changes to the HTTP response

Why do i specify a timeout in the Socket.select?

I have noticed that if i don't specify a timeout the Socket.select seems to block every so often for several seconds (2/5sec) and i have read in another post to specify a timeout value to the call.

I'll retest now.


November 15, 2017
So thanks for the suggestions,  i have fixed HTTP response not postman cal also parse the headers correctly!! happy days.

I have removed the duration from the Socket.select but the application seems to process a bunch or requests and then it stalls for several seconds (3/5) and then it resumes.

The httpclinet which i'm using to test the application is reporting: "connection timeout".

Could this be caused by the GC?

```updated code
import std.algorithm : remove;
import std.conv : to;
import core.thread: Thread;
import std.socket : InternetAddress, Socket, SocketException, SocketSet, TcpSocket, SocketShutdown;
import core.time : Duration, dur;
import std.stdio : writeln, writefln;
import std.parallelism : task, TaskPool;

string to_retlf (string s)
{
   import std.algorithm;
   import std.string;
   return s
      .lineSplitter
      .map!(a => chomp (a))
      .join ("\r\n");
}

void main(string[] args)
{
    ushort port;

    if (args.length >= 2)
        port = to!ushort(args[1]);
    else
        port = 4444;

    auto listener = new TcpSocket();
    assert(listener.isAlive);
    listener.blocking = false;
    listener.bind(new InternetAddress(port));
    listener.listen(100);
    writefln("Listening on port %d.", port);

    auto taskPool = new TaskPool(8);

    string response = "HTTP/1.1 200 OK
Server: dland:v2.076.1
Date: Tue, 11 Nov 2017 15:56:02 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 51

<html><head></head><body>Hello World!</body></html>".to_retlf;

    new Thread({
        auto listeningSet = new SocketSet();
        while(true) {
            listeningSet.add(listener);
            if (Socket.select(listeningSet, null, null)) {
                if (listeningSet.isSet(listener))        // connection request
                {
                    Socket socket = listener.accept();
                    assert(socket.isAlive);
                    //writefln("Connection from %s established.", socket.remoteAddress().toString());
                    auto task = task!handle_socket(socket, response);
                    taskPool.put(task);
                }
            }
            listeningSet.reset();
        }
    }).start();
}



void handle_socket(Socket socket, string response) {
    auto socketSet = new SocketSet();
    while(true) {
        socketSet.add(socket);
        if (Socket.select(socketSet, null, null)) {
            char[1024] buf;
            auto datLength = socket.receive(buf[]);

            if (datLength == Socket.ERROR)
                writeln("Connection error.");
            else if (datLength != 0)
            {
                //writefln("Received %d bytes from %s: \"%s\"", datLength, socket.remoteAddress().toString(), buf[0..datLength]);
                //writefln("Writing response");
                socket.send(response);
            }
            // release socket resources now

            socket.close();

            break;

        }
        socketSet.reset();
    }
```

Regards







November 15, 2017
Do not use your own taskPool, just use global taskPool proerty (import
std.parallelism: taskPool).

You should not set blocking to false. And dont use Thread here.  There is no reason to do that.  Just move that code into the main

Dne 15. 11. 2017 12:15 odp. napsal uživatel "ade90036 via Digitalmars-d-learn" <digitalmars-d-learn@puremagic.com>:

So thanks for the suggestions,  i have fixed HTTP response not postman cal also parse the headers correctly!! happy days.

I have removed the duration from the Socket.select but the application seems to process a bunch or requests and then it stalls for several seconds (3/5) and then it resumes.

The httpclinet which i'm using to test the application is reporting: "connection timeout".

Could this be caused by the GC?

```updated code

import std.algorithm : remove;
import std.conv : to;
import core.thread: Thread;
import std.socket : InternetAddress, Socket, SocketException, SocketSet,
TcpSocket, SocketShutdown;
import core.time : Duration, dur;
import std.stdio : writeln, writefln;
import std.parallelism : task, TaskPool;

string to_retlf (string s)
{
   import std.algorithm;
   import std.string;
   return s
      .lineSplitter
      .map!(a => chomp (a))
      .join ("\r\n");
}

void main(string[] args)
{
    ushort port;

    if (args.length >= 2)
        port = to!ushort(args[1]);
    else
        port = 4444;


    auto listener = new TcpSocket();
    assert(listener.isAlive);
    listener.blocking = false;
    listener.bind(new InternetAddress(port));
    listener.listen(100);
    writefln("Listening on port %d.", port);

    auto taskPool = new TaskPool(8);

    string response = "HTTP/1.1 200 OK

Server: dland:v2.076.1
Date: Tue, 11 Nov 2017 15:56:02 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 51

<html><head></head><body>Hello World!</body></html>".to_retlf;


    new Thread({
        auto listeningSet = new SocketSet();
        while(true) {
            listeningSet.add(listener);
            if (Socket.select(listeningSet, null, null)) {

                if (listeningSet.isSet(listener))        // connection
request
                {
                    Socket socket = listener.accept();
                    assert(socket.isAlive);

                    //writefln("Connection from %s established.",
socket.remoteAddress().toString());
                    auto task = task!handle_socket(socket, response);
                    taskPool.put(task);
                }
            }
            listeningSet.reset();
        }
    }).start();
}



void handle_socket(Socket socket, string response) {

    auto socketSet = new SocketSet();
    while(true) {
        socketSet.add(socket);
        if (Socket.select(socketSet, null, null)) {

            char[1024] buf;
            auto datLength = socket.receive(buf[]);

            if (datLength == Socket.ERROR)
                writeln("Connection error.");
            else if (datLength != 0)
            {
                //writefln("Received %d bytes from %s: \"%s\"", datLength,
socket.remoteAddress().toString(), buf[0..datLength]);
                //writefln("Writing response");
                socket.send(response);

            }
            // release socket resources now

            socket.close();

            break;

        }
        socketSet.reset();
    }
```

Regards


November 15, 2017
This one works ok for me, but I am on linux: https://dpaste.dzfl.pl/f54decee45bc

On Wed, Nov 15, 2017 at 12:46 PM, Daniel Kozak <kozzi11@gmail.com> wrote:

> Do not use your own taskPool, just use global taskPool proerty (import
> std.parallelism: taskPool).
>
> You should not set blocking to false. And dont use Thread here.  There is no reason to do that.  Just move that code into the main
>
> Dne 15. 11. 2017 12:15 odp. napsal uživatel "ade90036 via Digitalmars-d-learn" <digitalmars-d-learn@puremagic.com>:
>
> So thanks for the suggestions,  i have fixed HTTP response not postman cal also parse the headers correctly!! happy days.
>
> I have removed the duration from the Socket.select but the application seems to process a bunch or requests and then it stalls for several seconds (3/5) and then it resumes.
>
> The httpclinet which i'm using to test the application is reporting: "connection timeout".
>
> Could this be caused by the GC?
>
> ```updated code
>
> import std.algorithm : remove;
> import std.conv : to;
> import core.thread: Thread;
> import std.socket : InternetAddress, Socket, SocketException, SocketSet,
> TcpSocket, SocketShutdown;
> import core.time : Duration, dur;
> import std.stdio : writeln, writefln;
> import std.parallelism : task, TaskPool;
>
> string to_retlf (string s)
> {
>    import std.algorithm;
>    import std.string;
>    return s
>       .lineSplitter
>       .map!(a => chomp (a))
>       .join ("\r\n");
> }
>
> void main(string[] args)
> {
>     ushort port;
>
>     if (args.length >= 2)
>         port = to!ushort(args[1]);
>     else
>         port = 4444;
>
>
>     auto listener = new TcpSocket();
>     assert(listener.isAlive);
>     listener.blocking = false;
>     listener.bind(new InternetAddress(port));
>     listener.listen(100);
>     writefln("Listening on port %d.", port);
>
>     auto taskPool = new TaskPool(8);
>
>     string response = "HTTP/1.1 200 OK
>
> Server: dland:v2.076.1
> Date: Tue, 11 Nov 2017 15:56:02 GMT
> Content-Type: text/html; charset=UTF-8
> Content-Length: 51
>
> <html><head></head><body>Hello World!</body></html>".to_retlf;
>
>
>     new Thread({
>         auto listeningSet = new SocketSet();
>         while(true) {
>             listeningSet.add(listener);
>             if (Socket.select(listeningSet, null, null)) {
>
>                 if (listeningSet.isSet(listener))        // connection
> request
>                 {
>                     Socket socket = listener.accept();
>                     assert(socket.isAlive);
>
>                     //writefln("Connection from %s established.",
> socket.remoteAddress().toString());
>                     auto task = task!handle_socket(socket, response);
>                     taskPool.put(task);
>                 }
>             }
>             listeningSet.reset();
>         }
>     }).start();
> }
>
>
>
> void handle_socket(Socket socket, string response) {
>
>     auto socketSet = new SocketSet();
>     while(true) {
>         socketSet.add(socket);
>         if (Socket.select(socketSet, null, null)) {
>
>             char[1024] buf;
>             auto datLength = socket.receive(buf[]);
>
>             if (datLength == Socket.ERROR)
>                 writeln("Connection error.");
>             else if (datLength != 0)
>             {
>                 //writefln("Received %d bytes from %s: \"%s\"", datLength,
> socket.remoteAddress().toString(), buf[0..datLength]);
>                 //writefln("Writing response");
>                 socket.send(response);
>
>             }
>             // release socket resources now
>
>             socket.close();
>
>             break;
>
>         }
>         socketSet.reset();
>     }
> ```
>
> Regards
>
>
>
>
>
>
>
>
>


November 15, 2017
And this one
https://paste.ofcode.org/KNqxcrmACLZLseB45MvwC

Here you can test if threads makes difference
when compile with:
    dmd -O -release -version=SINGLE_THREAD xxx.d
it will use only one thread
when compile with:
    dmd -O -release xxx.d
it will use thread pool

On Wed, Nov 15, 2017 at 2:31 PM, Daniel Kozak <kozzi11@gmail.com> wrote:

> This one works ok for me, but I am on linux: https://dpaste.dzfl.pl/f54decee45bc
>
> On Wed, Nov 15, 2017 at 12:46 PM, Daniel Kozak <kozzi11@gmail.com> wrote:
>
>> Do not use your own taskPool, just use global taskPool proerty (import
>> std.parallelism: taskPool).
>>
>> You should not set blocking to false. And dont use Thread here.  There is no reason to do that.  Just move that code into the main
>>
>> Dne 15. 11. 2017 12:15 odp. napsal uživatel "ade90036 via Digitalmars-d-learn" <digitalmars-d-learn@puremagic.com>:
>>
>> So thanks for the suggestions,  i have fixed HTTP response not postman cal also parse the headers correctly!! happy days.
>>
>> I have removed the duration from the Socket.select but the application seems to process a bunch or requests and then it stalls for several seconds (3/5) and then it resumes.
>>
>> The httpclinet which i'm using to test the application is reporting: "connection timeout".
>>
>> Could this be caused by the GC?
>>
>> ```updated code
>>
>> import std.algorithm : remove;
>> import std.conv : to;
>> import core.thread: Thread;
>> import std.socket : InternetAddress, Socket, SocketException, SocketSet,
>> TcpSocket, SocketShutdown;
>> import core.time : Duration, dur;
>> import std.stdio : writeln, writefln;
>> import std.parallelism : task, TaskPool;
>>
>> string to_retlf (string s)
>> {
>>    import std.algorithm;
>>    import std.string;
>>    return s
>>       .lineSplitter
>>       .map!(a => chomp (a))
>>       .join ("\r\n");
>> }
>>
>> void main(string[] args)
>> {
>>     ushort port;
>>
>>     if (args.length >= 2)
>>         port = to!ushort(args[1]);
>>     else
>>         port = 4444;
>>
>>
>>     auto listener = new TcpSocket();
>>     assert(listener.isAlive);
>>     listener.blocking = false;
>>     listener.bind(new InternetAddress(port));
>>     listener.listen(100);
>>     writefln("Listening on port %d.", port);
>>
>>     auto taskPool = new TaskPool(8);
>>
>>     string response = "HTTP/1.1 200 OK
>>
>> Server: dland:v2.076.1
>> Date: Tue, 11 Nov 2017 15:56:02 GMT
>> Content-Type: text/html; charset=UTF-8
>> Content-Length: 51
>>
>> <html><head></head><body>Hello World!</body></html>".to_retlf;
>>
>>
>>     new Thread({
>>         auto listeningSet = new SocketSet();
>>         while(true) {
>>             listeningSet.add(listener);
>>             if (Socket.select(listeningSet, null, null)) {
>>
>>                 if (listeningSet.isSet(listener))        // connection
>> request
>>                 {
>>                     Socket socket = listener.accept();
>>                     assert(socket.isAlive);
>>
>>                     //writefln("Connection from %s established.",
>> socket.remoteAddress().toString());
>>                     auto task = task!handle_socket(socket, response);
>>                     taskPool.put(task);
>>                 }
>>             }
>>             listeningSet.reset();
>>         }
>>     }).start();
>> }
>>
>>
>>
>> void handle_socket(Socket socket, string response) {
>>
>>     auto socketSet = new SocketSet();
>>     while(true) {
>>         socketSet.add(socket);
>>         if (Socket.select(socketSet, null, null)) {
>>
>>             char[1024] buf;
>>             auto datLength = socket.receive(buf[]);
>>
>>             if (datLength == Socket.ERROR)
>>                 writeln("Connection error.");
>>             else if (datLength != 0)
>>             {
>>                 //writefln("Received %d bytes from %s: \"%s\"",
>> datLength, socket.remoteAddress().toString(), buf[0..datLength]);
>>                 //writefln("Writing response");
>>                 socket.send(response);
>>
>>             }
>>             // release socket resources now
>>
>>             socket.close();
>>
>>             break;
>>
>>         }
>>         socketSet.reset();
>>     }
>> ```
>>
>> Regards
>>
>>
>>
>>
>>
>>
>>
>>
>>
>


November 15, 2017
On Wednesday, 15 November 2017 at 14:22:51 UTC, Daniel Kozak wrote:
> And this one
> https://paste.ofcode.org/KNqxcrmACLZLseB45MvwC

I thrash your code with two shell processes

```
    while true; do curl 127.0.0.1:4444 -o /dev/null; done
```

running parallel. Using strace -fFeclose on the binary this happens frequently:

```
:
[pid 10926] close(5)                    = 0
[pid 10926] close(4)                    = 0
[pid 10926] close(4)                    = 0
[pid 10926] close(4)                    = 0
[pid 10926] close(4)                    = 0
[pid 10926] --- SIGUSR1 (User defined signal 1) @ 0 (0) ---
[pid 10926] --- SIGUSR2 (User defined signal 2) @ 0 (0) ---
[pid 10919] close(4294967295)           = -1 EBADF (Bad file descriptor)
[pid 10919] close(4294967295)           = -1 EBADF (Bad file descriptor)
[pid 10919] close(4294967295)           = -1 EBADF (Bad file descriptor)
:
[pid 10919] close(4294967295)           = -1 EBADF (Bad file descriptor)
[pid 10919] close(4294967295)           = -1 EBADF (Bad file descriptor)
[pid 10919] close(4294967295)           = -1 EBADF (Bad file descriptor)
[pid 10926] close(4)                    = 0
[pid 10926] close(4)                    = 0
[pid 10926] close(4)                    = 0
:
```

The destructor of Socket is trying to close the already closed socket? If the worker I change

```
           socket.close();
```

to

```
            destroy (socket);
```

these close(-1) calls go away.
November 15, 2017
On Wednesday, 15 November 2017 at 13:31:46 UTC, Daniel Kozak wrote:
> This one works ok for me, but I am on linux: https://dpaste.dzfl.pl/f54decee45bc

It works, but it does not handle two connects in parallel. STR:

1. start the binary in console 1
2. telnet localhost 4444 in console 2
3. telnet localhost 4444 in console 3
4. enter a [RETURN] in console (3)

observed: nothing (the "thread" handling the first connect blocks)
expected: response

On my machine defaultPoolThreads() returns 1. This can easily be increased:

```
   defaultPoolThreads(8);
```

There is also another problem with Socket.select: It may return -1. This frequently happens when the ACCEPT socket is non-blocking and the select is interrupted (by the GC?) then errno == EINTR.

Also not having a timeout in the Socket.select of handle_socket allows for DOS attacks like the one above. In case of a timeout select also returns -1.
« First   ‹ Prev
1 2 3