0

In a previous question, I asked how to implement asynchronous I/O. This code now works, except that at the end it never stops. It seems that aio_read reads starting at offset, for length, and if it is past the end of the file, the operation succeeds? This code builds and runs on Ubuntu 20.04LTS and successfully reads blocks 1-5, each 512 bytes, then when it runs out of file it keeps oscillating between block 4 and 5. It never terminates.

Here is the code:

#include <aio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#include <condition_variable>
#include <cstring>
#include <iostream>
#include <thread>

using namespace std;
using namespace std::chrono_literals;

constexpr uint32_t blockSize = 512;

mutex readMutex;
bool readReady = false;
condition_variable cv;
bool operation_completed = false;

int fh;
int bytesRead;

void process(char* buf, uint32_t bytesRead) {
  cout << "processing..." << endl;
  usleep(100000);
}

void aio_completion_handler(sigval_t sigval) {
  struct aiocb* req = (struct aiocb*)sigval.sival_ptr;

  // check whether asynch operation is complete
  int status;
  if ((status = aio_error(req)) != 0) {
    cout << "Error: " << status << '\n';
    return;
  }
  int ret = aio_return(req);
    bytesRead = req->aio_nbytes;
  cout << "ret == " << ret << endl;
  cout << (char*)req->aio_buf << endl;
  unique_lock<mutex> readLock(readMutex);
  operation_completed = true;
  cv.notify_one();
}

void thready() {
  char* buf1 = new char[blockSize];
  char* buf2 = new char[blockSize];
  aiocb cb;
  char* processbuf = buf1;
  char* readbuf = buf2;
  fh = open("smallfile.dat", O_RDONLY);
  if (fh < 0) {
    throw std::runtime_error("cannot open file!");
  }

  memset(&cb, 0, sizeof(aiocb));
  cb.aio_fildes = fh;
  cb.aio_nbytes = blockSize;
  cb.aio_offset = 0;

  // Fill in callback information
  /*
  Using SIGEV_THREAD to request a thread callback function as a notification
  method
  */
  cb.aio_sigevent.sigev_notify_attributes = nullptr;
  cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
  cb.aio_sigevent.sigev_notify_function = aio_completion_handler;
  /*
  The context to be transmitted is loaded into the handler (in this case, a
  reference to the aiocb request itself). In this handler, we simply refer to
  the arrived sigval pointer and use the AIO function to verify that the request
  has been completed.
  */
  cb.aio_sigevent.sigev_value.sival_ptr = &cb;

  int cursor = 0;
  int currentBytesRead = read(fh, buf1, blockSize);  // read the 1st block

  while (true) {
    cb.aio_buf = readbuf;
    operation_completed = false; // set predicate to true and wait until asynch changes it
    cb.aio_offset = cursor;
    aio_read(&cb);  // each next block is read asynchronously
    process(processbuf, currentBytesRead);  // process while waiting
    {
      unique_lock<mutex> readLock(readMutex);
      cv.wait( readLock, []{ return operation_completed; } );
    }
    if (!operation_completed)
      break;
    currentBytesRead = bytesRead; // make local copy of global modified by the asynch code
    cursor += bytesRead;
    if (currentBytesRead < blockSize) {
      break;  // last time, get out
    }
    cout << "back from wait" << endl;
    swap(processbuf, readbuf);     // switch to other buffer for next time
    currentBytesRead = bytesRead;  // create local copy
  }

  delete[] buf1;
  delete[] buf2;
}

int main() {
  try {
    thready();
  } catch (std::exception& e) {
    cerr << e.what() << '\n';
  }
  return 0;
}

First, is the above code an appropriate way to do this to get the length of the file and figure out exactly how many reads to do?

Second, if this is so, fine, but how can aio_read just return success if I try to read past the end of file? Error status is always zero. I am confused about what it is supposed to do.

with 512 bytes of each of 1,2,3,4,5

Dov
  • 8,000
  • 8
  • 46
  • 75
  • I am not well-versed in async IO, however I can tell you that `req->aio_nbytes` is not set to the number of bytes read. It is simply the size of the read buffer, which is always 512. I think you maybe meant to do `bytesRead = aio_return(req);`. The reason it goes back and forth between blocks 4 and 5 is because you never clear those buffers, so when it doesn't write anything into them it simply swaps the existing data in the read buffers and then continues. Note that even with this fix you will still echo block 4 twice, since you `cout` in `aio_completion_handler` before the read length check. – Layne Bernardo Sep 06 '22 at 02:22
  • thanks @LayneBernardo but the error is always zero. Shouldn't there be an error if I try to read past end of file? – Dov Sep 06 '22 at 09:18
  • 1
    When using `aio_return`, it returns the value that *would* be returned from the synchronous version of the asynchronous command you are running, or `-1` if there is an error. Since you are doing an async `read`, `aio_return` will return the number of bytes read from the file. It returns `512` for each block, and once you reach the end of the file, it will start returning zero indicating that zero bytes have been read. To get the actual error code in the event of an error, you would call `aio_error` after having received a `-1` from `aio_return`. Not sure why it doesn't return `-1` for EOF. – Layne Bernardo Sep 06 '22 at 11:13

0 Answers0