简体   繁体   English

Boost Asio async_read 有时会在阅读时挂起,但并非总是如此

[英]Boost Asio async_read sometimes hangs while reading but not always

I am implementing a small distributed system that consists N machines.我正在实现一个由 N 台机器组成的小型分布式系统。 Each of them receives some data from some remote server and then propagates the data to other n-1 fellow machines.他们每个人都从某个远程服务器接收一些数据,然后将数据传播到其他 n-1 台机器。 I am using the Boost Asio async_read and async_write to implement this.我正在使用 Boost Asio async_read 和 async_write 来实现这一点。 I set up a test cluster of N=30 machines.我建立了一个由 N=30 台机器组成的测试集群。 When I tried smaller datesets (receiving 75KB to 750KB per machine), the program always worked.当我尝试较小的数据集(每台机器接收 75KB 到 750KB)时,程序总是有效。 But when I moved on to just a slightly larger dataset (7.5MB), I observed strange behavior: at the beginning, reads and writes happened as expected, but after a while, some machines hanged while others finished, the number of machines that hanged varied with each run.但是当我转移到一个稍微大一点的数据集(7.5MB)时,我观察到了奇怪的行为:开始时,读取和写入按预期进行,但过了一段时间,一些机器挂了,而另一些机器完成了,挂起的机器数量每次运行都不同。 I tried to print out some messages in each handler and found that for those machines that hanged, async_read basically could not successfully read after a while, therefore nothing could proceed afterwards.我尝试在每个处理程序中打印出一些消息,发现对于那些挂起的机器,async_read 一段时间后基本上无法成功读取,因此之后无法进行任何操作。 I checked the remote servers, and they all finished writing.我检查了远程服务器,他们都写完了。 I have tried out using strand to control the order of execution of async reads and writes, and I also tried using different io_services for read and write.我尝试过使用链来控制异步读取和写入的执行顺序,并且我还尝试使用不同的 io_services 进行读取和写入。 None of them solved the problem.他们都没有解决问题。 I am pretty desperate.我很绝望。 Can anyone help me?谁能帮我?

Here is the code for the class that does the read and propagation:这是执行读取和传播的类的代码:

const int TRANS_TUPLE_SIZE=15;
const int TRANS_BUFFER_SIZE=5120/TRANS_TUPLE_SIZE*TRANS_TUPLE_SIZE;
class Asio_Trans_Broadcaster
{
private:
   char buffer[TRANS_BUFFER_SIZE];
   int node_id;
   int mpi_size;
   int mpi_rank;
   boost::asio::ip::tcp::socket* dbsocket;
   boost::asio::ip::tcp::socket** sender_sockets;
   int n_send;
   boost::mutex mutex;
   bool done;
public:
   Asio_Trans_Broadcaster(boost::asio::ip::tcp::socket* dbskt, boost::asio::ip::tcp::socket** senderskts,
        int msize, int mrank, int id)
{
    dbsocket=dbskt;
    count=0;
    node_id=id;
    mpi_size=mpi_rank=-1;
    sender_sockets=senderskts;
    mpi_size=msize;
    mpi_rank=mrank;
    n_send=-1;
    done=false;
}

static std::size_t completion_condition(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    int remain=bytes_transferred%TRANS_TUPLE_SIZE;
    if(remain==0 && bytes_transferred>0)
        return 0;
    else
        return TRANS_BUFFER_SIZE-bytes_transferred;
}


void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred)
{
    int n=-1;
    mutex.lock();
    n_send--;
    n=n_send;
    mutex.unlock();
    fprintf(stdout, "~~~~~~ @%d, write_handler: %d bytes, copies_to_send: %d\n",
                                    node_id, bytes_transferred, n);
    if(n==0 && !done)
        boost::asio::async_read(*dbsocket,
            boost::asio::buffer(buffer, TRANS_BUFFER_SIZE),
            Asio_Trans_Broadcaster::completion_condition, boost::bind(&Asio_Trans_Broadcaster::broadcast_handler, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
}

void broadcast_handler(const boost::system::error_code &ec, std::size_t bytes_transferred)
{
    fprintf(stdout, "@%d, broadcast_handler: %d bytes, mpi_size:%d, mpi_rank: %d\n", node_id, bytes_transferred, mpi_size, mpi_rank);
    if (!ec)
    {
        int pos=0;
        while(pos<bytes_transferred && pos<TRANS_BUFFER_SIZE)
        {
            int id=-1;
            memcpy(&id, &buffer[pos], 4);
            if(id<0)
            {
                done=true;
                fprintf(stdout, "@%d, broadcast_handler: done!\n", mpi_rank);
                break;
            }

            pos+=TRANS_TUPLE_SIZE;
        }

        mutex.lock();
        n_send=mpi_size-1;
        mutex.unlock();
        for(int i=0; i<mpi_size; i++)
            if(i!=mpi_rank)
            {
                boost::asio::async_write(*sender_sockets[i], boost::asio::buffer(buffer, bytes_transferred),
                                boost::bind(&Asio_Trans_Broadcaster::write_handler, this,
                                boost::asio::placeholders::error,
                                boost::asio::placeholders::bytes_transferred));
            }
    }
    else
    {
        cerr<<mpi_rank<<" error: "<<ec.message()<<endl;
      delete this;
    }


}

void broadcast()
{
    boost::asio::async_read(*dbsocket,
            boost::asio::buffer(buffer, TRANS_BUFFER_SIZE),
            Asio_Trans_Broadcaster::completion_condition, boost::bind(&Asio_Trans_Broadcaster::broadcast_handler, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
}
};

Here is the main code running on each machine:这是在每台机器上运行的主要代码:

int N=30;
boost::asio::io_service* sender_io_service=new boost::asio::io_service();
boost::asio::io_service::work* p_work=new boost::asio::io_service::work(*sender_io_service);
boost::thread_group send_thread_pool;
for(int i=0; i<NUM_THREADS; i++)
{
    send_thread_pool.create_thread( boost::bind( & boost::asio::io_service::run, sender_io_service ) );
}

boost::asio::io_service* receiver_io_service=new boost::asio::io_service();
shared_ptr<boost::asio::io_service::work> p_work2(new boost::asio::io_service::work(*receiver_io_service));
boost::thread_group thread_pool2;
thread_pool2.create_thread( boost::bind( & boost::asio::io_service::run, receiver_io_service) );

boost::asio::ip::tcp::socket* receiver_socket;
    //establish nonblocking connection with remote server
AsioConnectToRemote(5000, 1, receiver_io_service, receiver_socket, true);

boost::asio::ip::tcp::socket* send_sockets[N];
    //establish blocking connection with other machines
hadoopNodes = SetupAsioConnectionsWIthOthers(sender_io_service, send_sockets, hostFileName, mpi_rank, mpi_size, 3000, false);

Asio_Trans_Broadcaster* db_receiver=new Asio_Trans_Broadcaster(receiver_socket, send_sockets,
mpi_size,  mpi_rank, mpi_rank);

db_receiver->broadcast();
  p_work2.reset();
  thread_pool2.join_all();
  delete p_work;
send_thread_pool.join_all();

I don't know what your code is trying to achieve.我不知道你的代码试图实现什么。 There are too many missing bits.丢失的位太多了。

Of course, if the task is to asynchronously send/receive traffic on network sockets, Asio is just the thing for that.当然,如果任务是在网络套接字上异步发送/接收流量,那么 Asio 就是这样做的。 It's hard to see what's special about your code.很难看出您的代码有什么特别之处。

I'd suggest to clean up the more obvious problems:我建议清理更明显的问题:

  • there's (almost) no error handling (check your error_code -s!) (几乎)没有错误处理(检查你的error_code -s!)
  • unless you're on a funny platform, your format strings should use %lu for size_t除非你在一个有趣的平台上,否则你的格式字符串应该使用%lu作为size_t
  • why do you mess around with raw arrays, with possibly bad sizes, when you can just have a vector?当你可以只拥有一个向量时,你为什么要弄乱原始数组,可能有错误的大小?
  • never assume the size of objects if you can use sizeof:如果您可以使用 sizeof,则永远不要假设对象的大小:

     memcpy(&id, &trans_buffer[pos], sizeof(id));
  • come to think of it, it looks like the indexing of buffer is unsafe anyways:想想看,缓冲区的索引无论如何都是不安全的:

     while(pos < bytes_transferred && pos < TRANS_BUFFER_SIZE) { int id = -1; memcpy(&id, &buffer[pos], sizeof(id));

    If eg pos == TRANS_BUFFER_SIZE-1 here the memcpy invokes Undefined Behavour...如果例如pos == TRANS_BUFFER_SIZE-1此处 memcpy 调用未定义的行为...

  • why is there so much new going on?为什么会有这么多new事情发生? You're inviting a hairy class of bugs into your code.你在你的代码中引入了一堆毛茸茸的错误。 As if memory management wasn't the achilles heel of lowlevel coding.好像内存管理不是低级编码的致命弱点。 Use values, or shared pointers.使用值或共享指针。 Never delete this .永远不要delete this Ever [1]曾经[1]

  • why is there so much repeated code?为什么有这么多重复的代码? Why is one thread pool named after sender and the other thread_pool2 ?为什么一个线程池以sender命名,另一个线程池以thread_pool2命名? Which contains 1 thread.其中包含 1 个线程。 Eh?诶? Why do you have one work item as a raw pointer, the other as a shared_ptr ?为什么你有一个work项作为原始指针,另一个作为shared_ptr

    You could just just:你可以只:

     struct service_wrap { service_wrap(int threads) { while(threads--) pool.create_thread(boost::bind(&boost::asio::io_service::run, boost::ref(io_service))); } ~service_wrap() { io_service.post(boost::bind(&service_wrap::stop, this)); pool.join_all(); } private: // mind the initialization order! boost::asio::io_service io_service; boost::optional<boost::asio::io_service::work> work; boost::thread_group pool; void stop() { work = boost::none; } };

    So you can simply write:所以你可以简单地写:

     service_wrap senders(NUM_THREADS); service_wrap receivers(1);

    Wow.哇。 Did you see that?你看到了吗? No more chance of error.不再有出错的机会。 If you fix one pool, you fix the other automatically.如果您修复一个池,则会自动修复另一个池。 No more delete the first, .reset() the second work item.不再delete第一个, .reset()第二个work项。 In short: no more messy code, and less complexity.简而言之:不再有杂乱的代码,复杂性也更低。

  • Use exception safe locking guards:使用异常安全锁定防护:

     int local_n_send = -1; // not clear naming { boost::lock_guard<boost::mutex> lk(mutex); n_send--; local_n_send = n_send; }
  • the body of broadcast is completely repeated in write_handler() . broadcast的主体在write_handler()完全重复。 Why not just call it:为什么不直接调用它:

     if(local_n_send == 0 && !done) broadcast();
  • I think there's still a race condition - not a data race on the access to n_send itself, but the decision to re-broadcast might be wrong if n_send reaches zero after the the lock is released.我认为仍然存在竞争条件 - 不是访问n_send本身的数据竞争,但是如果在释放锁定后n_send达到零,重新广播的决定可能是错误的。 Now, since broadcast() does only an async operation, you can just do it under the lock and get rid of the race condition:现在,由于broadcast()只执行异步操作,您可以在锁定下执行并摆脱竞争条件:

     void write_handler(const error_code &ec, size_t bytes_transferred) { boost::lock_guard<boost::mutex> lk(mutex); if(!(done || --n_send)) broadcast(); }

    Woop woop.呜呜呜。 That's three lines of code now.现在是三行代码。 Less code is less bugs.更少的代码就是更少的错误。

My guess would be that if you diligently scrub the code like this, you will inevitably find your clues.我的猜测是,如果你像这样认真地擦洗代码,你将不可避免地找到你的线索。 Think of it like you would look for a lost wedding-ring: you wouldn't leave a mess lying around.把它想象成你会寻找丢失的结婚戒指:你不会在周围留下一团糟。 Instead, you'd go from room to room and tidy it all up.相反,你会从一个房间走到另一个房间,把它收拾干净。 Throw everything "out" first if need be.如果需要,首先将所有东西“扔掉”。

Iff you can make this thing self-contained /and/ reproducible, I'll even debug it further for you!当且仅当你可以让这件事情自足/和/重复性好,我会进一步调试它为您服务!

Cheers干杯

Here's a starting point that I made while looking at the code: Compiling on Coliru这是我在查看代码时所做的一个起点:在 Coliru 上编译

#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/array.hpp>
#include <boost/make_shared.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <iostream>

const/*expr*/ int TRANS_TUPLE_SIZE  = 15;
const/*expr*/ int TRANS_BUFFER_SIZE = 5120 / TRANS_TUPLE_SIZE * TRANS_TUPLE_SIZE;

namespace AsioTrans
{
    using boost::system::error_code;
    using namespace boost::asio;

    typedef ip::tcp::socket             socket_t;
    typedef boost::ptr_vector<socket_t> socket_list;

    class Broadcaster
    {
    private:
        boost::array<char, TRANS_BUFFER_SIZE> trans_buffer;

        int node_id;
        int mpi_rank;

        socket_t&    dbsocket;
        socket_list& sender_sockets;

        int n_send;
        boost::mutex mutex;
        bool done;
    public:
        Broadcaster(
            socket_t& dbskt,
            socket_list& senderskts,
            int mrank,
            int id) : 
                node_id(id),
                mpi_rank(mrank),
                dbsocket(dbskt),
                sender_sockets(senderskts),
                n_send(-1),
                done(false)
        {
            // count=0;
        }

        static size_t completion_condition(const error_code& error, size_t bytes_transferred)
        {
            // TODO FIXME handler error_code here
            int remain = bytes_transferred % TRANS_TUPLE_SIZE;

            if(bytes_transferred && !remain)
            {
                return 0;
            }
            else
            {
                return TRANS_BUFFER_SIZE - bytes_transferred;
            }
        }

        void write_handler(const error_code &ec, size_t bytes_transferred)
        {
            // TODO handle errors
            // TODO check bytes_transferred
            boost::lock_guard<boost::mutex> lk(mutex);

            if(!(done || --n_send))
                broadcast();
        }

        void broadcast_handler(const error_code &ec, size_t bytes_transferred)
        {
            fprintf(stdout, "@%d, broadcast_handler: %lu bytes, mpi_size:%lu, mpi_rank: %d\n", node_id, bytes_transferred, sender_sockets.size(), mpi_rank);

            if(!ec)
            {
                for(size_t pos = 0; (pos < bytes_transferred && pos < TRANS_BUFFER_SIZE); pos += TRANS_TUPLE_SIZE)
                {
                    int id = -1;
                    memcpy(&id, &trans_buffer[pos], sizeof(id));

                    if(id < 0)
                    {
                        done = true;
                        fprintf(stdout, "@%d, broadcast_handler: done!\n", mpi_rank);
                        break;
                    }
                }

                {
                    boost::lock_guard<boost::mutex> lk(mutex);
                    n_send = sender_sockets.size() - 1;
                }

                for(int i = 0; size_t(i) < sender_sockets.size(); i++)
                {
                    if(i != mpi_rank)
                    {
                        async_write(
                                sender_sockets[i], 
                                buffer(trans_buffer, bytes_transferred),
                                boost::bind(&Broadcaster::write_handler, this, placeholders::error, placeholders::bytes_transferred));
                    }
                }
            }
            else
            {
                std::cerr << mpi_rank << " error: " << ec.message() << std::endl;
                delete this;
            }
        }

        void broadcast()
        {
            async_read(
                    dbsocket,
                    buffer(trans_buffer),
                    Broadcaster::completion_condition, 
                    boost::bind(&Broadcaster::broadcast_handler, this,
                        placeholders::error,
                        placeholders::bytes_transferred));
        }
    };

    struct service_wrap {
        service_wrap(int threads) {
            while(threads--)
                _pool.create_thread(boost::bind(&io_service::run, boost::ref(_service)));
        }

        ~service_wrap() {
            _service.post(boost::bind(&service_wrap::stop, this));
            _pool.join_all();
        }

        io_service& service() { return _service; }

    private: // mind the initialization order!
        io_service                        _service;
        boost::optional<io_service::work> _work;
        boost::thread_group               _pool;

        void stop() { 
            _work = boost::none;
        }
    };

    extern void AsioConnectToRemote(int, int, io_service&, socket_t&, bool);
    extern void SetupAsioConnectionsWIthOthers(io_service&, socket_list&, std::string, int, bool);
}

int main()
{
    using namespace AsioTrans;

    // there's no use in increasing #threads unless there are blocking operations
    service_wrap senders(boost::thread::hardware_concurrency()); 
    service_wrap receivers(1);

    socket_t receiver_socket(receivers.service());
    AsioConnectToRemote(5000, 1, receivers.service(), receiver_socket, true);

    socket_list send_sockets(30);
    /*hadoopNodes =*/ SetupAsioConnectionsWIthOthers(senders.service(), send_sockets, "hostFileName", 3000, false);

    int mpi_rank = send_sockets.size();
    AsioTrans::Broadcaster db_receiver(receiver_socket, send_sockets, mpi_rank, mpi_rank);
    db_receiver.broadcast();
}

[1] No exceptions. [1]没有例外。 Except when there's an exception to the no-exceptions rule.除非无例外规则有例外。 Exception-ception.异常接收。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM