> NOOOO it's not.

It absolutely is. Sure, you can safely call fread() from multiple threads, but they will wait for each other to finish, they will not be reading from the underlying file simultaneously.

Also, it's not really possible to use this API thread-safely, especially in the presence of errors. For example, the following code may show exactly the problem I was talking about:

void thread1() {
int* buf1 = calloc(1000, sizeof(int));
while (true) {
int read = fread(buf1, sizeof(int), 1000, file);
if (read < 1000) {
printf("couldn't read whole object: ")
if (ferror(file)) {
printf("read error");
} else if (feof(file)) {
printf("end of file");
} else {
printf("no reason???"); //will be occasionally printed
}
} else {
printf("success");
}
}
}

void thread2() {
while (true) {
fseek(file, 0, 0);
}
}

> FILE streams are buffered, so you never know which fread() call was the "source" of the error. And there is absolutely no point at all in trying to relate errors on a file handle to the n'th or (n+1)'th call.

The buffering is irrelevant. Sure, it's possible that ferror(f) is true even if your data was read successfully (a confusing situation in itself). But, in a non-concurrent context, you can always tell which fread() failed and why - the first time that you get back less bytes than you requested, if the file is not at the end, a read error was encountered (possibly some time ago, as you say).

> Yes it does, unless you're willing to throw away the gains you got from concurrent operation (or with those fancy messy async frameworks, throw away at least a part of the gains). Retrieving the error or other return value requires synchronization.

If you don't care about the result, than you don't even need to handle errors. If you do care about the result, returning the error in the exact same way as the success result adds 0 extra overhead/synchronization. However, returning the result in one place and the errors in another (like fread() does) adds mental overhead.

> Better than checking each call individually, isn't it? Less code and faster to execute, because requests can be processed asynchronously. This is very much like the FILE API.

It's easier to follow than manually checking each call individually, but I would say it's overall much worse:

1. If you forget to check for mctx.bad() before using the buffers, you'll have a very hard to track error. I would have to be very mindful of this during code review.

2. If you wanted to do something like initializing an array of buffers, you would have to check if mctx.bad() multiple times (once after malloc'ing the array, and again after malloc'ing all the buffers).

3. If I want to sequence these operations (write 1337 to buffer2 only if I successfully wrote 666 to buffer1) or need all of the data to be written or the whole operation is useless (and I don't want to wait to write 2GB to some file only to then find out that the 1B write to the registry failed), I actually have to write more code than if I had exceptions, since I have to add checks after each operation to interrupt the others.

In contrast, here is how this would look like with C# async/await and typical exceptions:

using(var mctx = malloc_context()) {
var buffer1 = mctx.malloc_buffer(size1); //throws exception if it fails
var buffer2 = mctx.malloc_buffer(size2);
var buffers = mctx.malloc_buffer_array(100); //your solution was not allocating this, so didn't care about errors
for (var i = 0; i < 100; i++) {
buffers[i] = mctx.malloc_buffer(42);
}

//assuming we want the behavior you suggest, but buffer_write is sync

Task.WaitAll(new Task[] {
Task.Factory.StartNew(() => {
try {
buffer1.write(data, 666);
catch(IOException e1) {
//handle very specific problem?
}
}),
Task.Factory.StartNew(() => buffer2.write(data, 1337))
}); //throws AggregateException if any of the tasks throws IOException

//assuming we want to write all data and fail as soon as one thing fails:
buffer1.write(data, 666);
buffer2.write(data, 1337);

} //error or no error, everything is cleaned up and an error is signalled to higher layers if this failed

If you want asynchronous behavior, you should make it explicitly asynchronous.