#240

Replace use of iostream-based classes

Opendavmac314opened 3y ago

Unfortunately, C++ iostream facilities suck. They are heavyweight due to supporting different character encodings. They make it impossible to get useful error messages in a standardised way (the language spec allows for it, but the implementations apparently don't bother and map all error conditions to the same useless message). std::ios::failure extends std::system_error but doesn't use errno as the error code.

We should replace them (including use of cout/cerr) with a wrapper for POSIX file I/O.

Activity

30
mobin-20083y ago

Do you mean we need to use printf() and friends in all cases or replacing something like ifstream with pure C functions like open() or even both? for example: https://github.com/davmac314/dinit/blob/a6cff08795ffaea003c1e893c4b3ec96d81a6ae9/src/dinit-env.cc#L20-L26 to this?

// Read and set environment variables from a file. May throw std::bad_alloc, std::system_error.
void read_env_file(const char *env_file_path, bool log_warnings, environment &env)
{
    int env_file = open(env_file_path, O_RDONLY);
    if (env_file == -1) {
        return
    }
    //env_file.exceptions(std::ios::badbit);
mobin-20083y ago

Do you mean we need to use printf() and friends in all cases or replacing something like ifstream with pure C functions like open() or even both? for example: https://github.com/davmac314/dinit/blob/a6cff08795ffaea003c1e893c4b3ec96d81a6ae9/src/dinit-env.cc#L20-L26 to this?

// Read and set environment variables from a file. May throw std::bad_alloc, std::system_error.
void read_env_file(const char *env_file_path, bool log_warnings, environment &env)
{
    int env_file = open(env_file_path, O_RDONLY);
    if (env_file == -1) {
        return
    }
    //env_file.exceptions(std::ios::badbit);
davmac3143y ago

No, not necessarily, I meant replace use of iostream (including fstream etc) with a similar wrapper class which uses open and write/read internally. It is still good to use a class rather than use POSIX functions directly as it allows using the RAII idiom.

davmac3143y ago

No, not necessarily, I meant replace use of iostream (including fstream etc) with a similar wrapper class which uses open and write/read internally. It is still good to use a class rather than use POSIX functions directly as it allows using the RAII idiom.

mobin-20083y ago

I worked on it a bit and I really enjoy, @davmac314 Please assign this issue to me if you wish. for example I have a very basic implantation for replacing cout and cerr with very better code generation:

#ifndef DINIT_IOSTREAM_H
#define DINIT_IOSTREAM_H

#include <baseproc-sys.h>
#include <cstring>
#include <cmath>

// ToDo: Desc

namespace dio {

class ostream
{
    public:
        ostream operator<<(const char *msg)
        {
            bp_sys::write(1, msg, strlen(msg));
            // ToDo: Error handling
            return *this;
        }
        ostream operator<<(std::string msg)
        {
            bp_sys::write(1, msg.c_str(), msg.size());
            return *this;
        }
        ostream operator<<(int num)
        {
            char tmp[(int)((ceil(log10(num))+1)*sizeof(char))];
            sprintf(tmp, "%d", num);
            write(1, tmp, strlen(tmp));
            return *this;
        }
};

ostream cout;
// ToDo: cerr should print to fd 2 (stderr)
ostream cerr;
}

#endif

and dinitcheck compiled and ran with no issues. We need buffer for stdio?

mobin-20083y ago

I worked on it a bit and I really enjoy, @davmac314 Please assign this issue to me if you wish. for example I have a very basic implantation for replacing cout and cerr with very better code generation:

#ifndef DINIT_IOSTREAM_H
#define DINIT_IOSTREAM_H

#include <baseproc-sys.h>
#include <cstring>
#include <cmath>

// ToDo: Desc

namespace dio {

class ostream
{
    public:
        ostream operator<<(const char *msg)
        {
            bp_sys::write(1, msg, strlen(msg));
            // ToDo: Error handling
            return *this;
        }
        ostream operator<<(std::string msg)
        {
            bp_sys::write(1, msg.c_str(), msg.size());
            return *this;
        }
        ostream operator<<(int num)
        {
            char tmp[(int)((ceil(log10(num))+1)*sizeof(char))];
            sprintf(tmp, "%d", num);
            write(1, tmp, strlen(tmp));
            return *this;
        }
};

ostream cout;
// ToDo: cerr should print to fd 2 (stderr)
ostream cerr;
}

#endif

and dinitcheck compiled and ran with no issues. We need buffer for stdio?

q663y ago

the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces

low level file descriptor funcs are too primitive for reasonable i/o though, so the implementation may get relatively complex

q663y ago

the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces

low level file descriptor funcs are too primitive for reasonable i/o though, so the implementation may get relatively complex

mobin-20083y ago

the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces

With something like fmt?

mobin-20083y ago

the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces

With something like fmt?

davmac3142y ago

I disagree. put() (formerly _write()) only write on these situations:

Ok, I see I misread it. But:

  1. Message has \n (newline)

Why flush just because of newline? And:

  1. "endl" object be passed to write() (equals to writing \n and calling flush())

Why does it call flush() if writing '\n' already does flush?

Question: It's safe to assume that EINTR, EWOULDBLOCK and EAGAIN will be changed in one point? my flush() tries up to 10 times about those errnos. I afraid to they get stuck and the whole program get stuck on infinite while loop design.

The stream library doesn't have sufficient context to make a decision about how to handle these errors, so it should report them to the application. It should not try the operation again, it's for the application to decide whether that is the right thing to do.

It might have made sense to have a mode which ignores and retries on EINTR, but I wouldn't bother with that now. For EWOULDBLOCK/EAGAIN just repeating the operation is completely wrong, you will end up repeating the same operation (with the same error) in a tight loop and send CPU usage through the roof. These conditions can only be cleared by something external (eg another process on the other end of the pipe) so you can't ever rely on that happening in any timeframe at all.

davmac3142y ago

I disagree. put() (formerly _write()) only write on these situations:

Ok, I see I misread it. But:

  1. Message has \n (newline)

Why flush just because of newline? And:

  1. "endl" object be passed to write() (equals to writing \n and calling flush())

Why does it call flush() if writing '\n' already does flush?

Question: It's safe to assume that EINTR, EWOULDBLOCK and EAGAIN will be changed in one point? my flush() tries up to 10 times about those errnos. I afraid to they get stuck and the whole program get stuck on infinite while loop design.

The stream library doesn't have sufficient context to make a decision about how to handle these errors, so it should report them to the application. It should not try the operation again, it's for the application to decide whether that is the right thing to do.

It might have made sense to have a mode which ignores and retries on EINTR, but I wouldn't bother with that now. For EWOULDBLOCK/EAGAIN just repeating the operation is completely wrong, you will end up repeating the same operation (with the same error) in a tight loop and send CPU usage through the roof. These conditions can only be cleared by something external (eg another process on the other end of the pipe) so you can't ever rely on that happening in any timeframe at all.

davmac3142y ago

@mobin-2008 I will have a proper look over what you have when I get a chance (see comment above too; I think flush-on-newline probably isn't the right choice).

davmac3142y ago

@mobin-2008 I will have a proper look over what you have when I get a chance (see comment above too; I think flush-on-newline probably isn't the right choice).

mobin-20082y ago

Sounds good, I'm going to clear some codes and documention in 30 minutes.

mobin-20082y ago

Sounds good, I'm going to clear some codes and documention in 30 minutes.

mobin-20082y ago

@davmac314 I worked on it a bit and here's the list for pending things:

  1. istream class documention (global members are documented completely)
  2. fstream library documention (code is small and self explanatory but having documention is good)

I try to complete these things in 4 days when I get a chance

mobin-20082y ago

@davmac314 I worked on it a bit and here's the list for pending things:

  1. istream class documention (global members are documented completely)
  2. fstream library documention (code is small and self explanatory but having documention is good)

I try to complete these things in 4 days when I get a chance

davmac3142y ago

@mobin-2008

I try to complete these things in 4 days when I get a chance

Ok - please open a PR when you are ready. I had a quick look and the major concerns are gone, but I will still have a lot of comments/questions.

davmac3142y ago

@mobin-2008

I try to complete these things in 4 days when I get a chance

Ok - please open a PR when you are ready. I had a quick look and the major concerns are gone, but I will still have a lot of comments/questions.