forked from OSchip/llvm-project
[tsan] Add support for GCD IO channels on Darwin
This patch adds interceptors for dispatch_io_*, dispatch_read and dispatch_write functions. This avoids false positives when using GCD IO. Adding several test cases. Differential Revision: http://reviews.llvm.org/D21889 llvm-svn: 275071
This commit is contained in:
parent
8f1d408c74
commit
ddc3cc65cb
|
@ -36,6 +36,7 @@ typedef struct {
|
|||
bool free_context_in_callback;
|
||||
bool submitted_synchronously;
|
||||
bool is_barrier_block;
|
||||
uptr non_queue_sync_object;
|
||||
} tsan_block_context_t;
|
||||
|
||||
// The offsets of different fields of the dispatch_queue_t structure, exported
|
||||
|
@ -94,12 +95,13 @@ static tsan_block_context_t *AllocContext(ThreadState *thr, uptr pc,
|
|||
static void dispatch_callback_wrap(void *param) {
|
||||
SCOPED_INTERCEPTOR_RAW(dispatch_callback_wrap);
|
||||
tsan_block_context_t *context = (tsan_block_context_t *)param;
|
||||
dispatch_queue_t q = context->queue;
|
||||
bool is_queue_serial = context->queue && IsQueueSerial(context->queue);
|
||||
uptr sync_ptr = (uptr)context->queue ?: context->non_queue_sync_object;
|
||||
|
||||
uptr serial_sync = (uptr)q;
|
||||
uptr concurrent_sync = ((uptr)q) + sizeof(uptr);
|
||||
uptr serial_sync = (uptr)sync_ptr;
|
||||
uptr concurrent_sync = ((uptr)sync_ptr) + sizeof(uptr);
|
||||
uptr submit_sync = (uptr)context;
|
||||
bool serial_task = IsQueueSerial(q) || context->is_barrier_block;
|
||||
bool serial_task = context->is_barrier_block || is_queue_serial;
|
||||
|
||||
Acquire(thr, pc, submit_sync);
|
||||
Acquire(thr, pc, serial_sync);
|
||||
|
@ -148,7 +150,7 @@ static void invoke_and_release_block(void *param) {
|
|||
dispatch_block_t heap_block = Block_copy(block); \
|
||||
SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
|
||||
tsan_block_context_t new_context = { \
|
||||
q, heap_block, &invoke_and_release_block, false, true, barrier}; \
|
||||
q, heap_block, &invoke_and_release_block, false, true, barrier, 0}; \
|
||||
Release(thr, pc, (uptr)&new_context); \
|
||||
SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
|
||||
REAL(name##_f)(q, &new_context, dispatch_callback_wrap); \
|
||||
|
@ -174,7 +176,7 @@ static void invoke_and_release_block(void *param) {
|
|||
dispatch_function_t work) { \
|
||||
SCOPED_TSAN_INTERCEPTOR(name, q, context, work); \
|
||||
tsan_block_context_t new_context = { \
|
||||
q, context, work, false, true, barrier}; \
|
||||
q, context, work, false, true, barrier, 0}; \
|
||||
Release(thr, pc, (uptr)&new_context); \
|
||||
SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
|
||||
REAL(name)(q, &new_context, dispatch_callback_wrap); \
|
||||
|
@ -358,7 +360,7 @@ TSAN_INTERCEPTOR(void, dispatch_source_set_event_handler,
|
|||
return REAL(dispatch_source_set_event_handler)(source, nullptr);
|
||||
dispatch_queue_t q = GetTargetQueueFromSource(source);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, handler, &invoke_block, false, false, false };
|
||||
q, handler, &invoke_block, false, false, false, 0 };
|
||||
dispatch_block_t new_handler = Block_copy(^(void) {
|
||||
new_context.orig_context = handler; // To explicitly capture "handler".
|
||||
dispatch_callback_wrap(&new_context);
|
||||
|
@ -387,7 +389,7 @@ TSAN_INTERCEPTOR(void, dispatch_source_set_cancel_handler,
|
|||
return REAL(dispatch_source_set_cancel_handler)(source, nullptr);
|
||||
dispatch_queue_t q = GetTargetQueueFromSource(source);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, handler, &invoke_block, false, false, false };
|
||||
q, handler, &invoke_block, false, false, false, 0};
|
||||
dispatch_block_t new_handler = Block_copy(^(void) {
|
||||
new_context.orig_context = handler; // To explicitly capture "handler".
|
||||
dispatch_callback_wrap(&new_context);
|
||||
|
@ -418,7 +420,7 @@ TSAN_INTERCEPTOR(void, dispatch_source_set_registration_handler,
|
|||
return REAL(dispatch_source_set_registration_handler)(source, nullptr);
|
||||
dispatch_queue_t q = GetTargetQueueFromSource(source);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, handler, &invoke_block, false, false, false };
|
||||
q, handler, &invoke_block, false, false, false, 0};
|
||||
dispatch_block_t new_handler = Block_copy(^(void) {
|
||||
new_context.orig_context = handler; // To explicitly capture "handler".
|
||||
dispatch_callback_wrap(&new_context);
|
||||
|
@ -501,6 +503,179 @@ TSAN_INTERCEPTOR(dispatch_data_t, dispatch_data_create, const void *buffer,
|
|||
});
|
||||
}
|
||||
|
||||
typedef void (^fd_handler_t)(dispatch_data_t data, int error);
|
||||
typedef void (^cleanup_handler_t)(int error);
|
||||
|
||||
TSAN_INTERCEPTOR(void, dispatch_read, dispatch_fd_t fd, size_t length,
|
||||
dispatch_queue_t q, fd_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_read, fd, length, q, h);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
fd_handler_t new_h = Block_copy(^(dispatch_data_t data, int error) {
|
||||
new_context.orig_context = ^(void) {
|
||||
h(data, error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
REAL(dispatch_read)(fd, length, q, new_h);
|
||||
Block_release(new_h);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void, dispatch_write, dispatch_fd_t fd, dispatch_data_t data,
|
||||
dispatch_queue_t q, fd_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_write, fd, data, q, h);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
fd_handler_t new_h = Block_copy(^(dispatch_data_t data, int error) {
|
||||
new_context.orig_context = ^(void) {
|
||||
h(data, error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
REAL(dispatch_write)(fd, data, q, new_h);
|
||||
Block_release(new_h);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void, dispatch_io_read, dispatch_io_t channel, off_t offset,
|
||||
size_t length, dispatch_queue_t q, dispatch_io_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_read, channel, offset, length, q, h);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
dispatch_io_handler_t new_h =
|
||||
Block_copy(^(bool done, dispatch_data_t data, int error) {
|
||||
new_context.orig_context = ^(void) {
|
||||
h(done, data, error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
REAL(dispatch_io_read)(channel, offset, length, q, new_h);
|
||||
Block_release(new_h);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void, dispatch_io_write, dispatch_io_t channel, off_t offset,
|
||||
dispatch_data_t data, dispatch_queue_t q,
|
||||
dispatch_io_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_write, channel, offset, data, q, h);
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
dispatch_io_handler_t new_h =
|
||||
Block_copy(^(bool done, dispatch_data_t data, int error) {
|
||||
new_context.orig_context = ^(void) {
|
||||
h(done, data, error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
REAL(dispatch_io_write)(channel, offset, data, q, new_h);
|
||||
Block_release(new_h);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void, dispatch_io_barrier, dispatch_io_t channel,
|
||||
dispatch_block_t barrier) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_barrier, channel, barrier);
|
||||
__block tsan_block_context_t new_context = {
|
||||
nullptr, nullptr, &invoke_block, false, false, false, 0};
|
||||
new_context.non_queue_sync_object = (uptr)channel;
|
||||
new_context.is_barrier_block = true;
|
||||
dispatch_block_t new_block = Block_copy(^(void) {
|
||||
new_context.orig_context = ^(void) {
|
||||
barrier();
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
REAL(dispatch_io_barrier)(channel, new_block);
|
||||
Block_release(new_block);
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create, dispatch_io_type_t type,
|
||||
dispatch_fd_t fd, dispatch_queue_t q, cleanup_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_create, type, fd, q, h);
|
||||
__block dispatch_io_t new_channel = nullptr;
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
cleanup_handler_t new_h = Block_copy(^(int error) {
|
||||
{
|
||||
SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback);
|
||||
Acquire(thr, pc, (uptr)new_channel); // Release() in dispatch_io_close.
|
||||
}
|
||||
new_context.orig_context = ^(void) {
|
||||
h(error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
new_channel = REAL(dispatch_io_create)(type, fd, q, new_h);
|
||||
Block_release(new_h);
|
||||
return new_channel;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create_with_path,
|
||||
dispatch_io_type_t type, const char *path, int oflag,
|
||||
mode_t mode, dispatch_queue_t q, cleanup_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_create_with_path, type, path, oflag, mode,
|
||||
q, h);
|
||||
__block dispatch_io_t new_channel = nullptr;
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
cleanup_handler_t new_h = Block_copy(^(int error) {
|
||||
{
|
||||
SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback);
|
||||
Acquire(thr, pc, (uptr)new_channel); // Release() in dispatch_io_close.
|
||||
}
|
||||
new_context.orig_context = ^(void) {
|
||||
h(error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
new_channel =
|
||||
REAL(dispatch_io_create_with_path)(type, path, oflag, mode, q, new_h);
|
||||
Block_release(new_h);
|
||||
return new_channel;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create_with_io,
|
||||
dispatch_io_type_t type, dispatch_io_t io, dispatch_queue_t q,
|
||||
cleanup_handler_t h) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_create_with_io, type, io, q, h);
|
||||
__block dispatch_io_t new_channel = nullptr;
|
||||
__block tsan_block_context_t new_context = {
|
||||
q, nullptr, &invoke_block, false, false, false, 0};
|
||||
cleanup_handler_t new_h = Block_copy(^(int error) {
|
||||
{
|
||||
SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback);
|
||||
Acquire(thr, pc, (uptr)new_channel); // Release() in dispatch_io_close.
|
||||
}
|
||||
new_context.orig_context = ^(void) {
|
||||
h(error);
|
||||
};
|
||||
dispatch_callback_wrap(&new_context);
|
||||
});
|
||||
uptr submit_sync = (uptr)&new_context;
|
||||
Release(thr, pc, submit_sync);
|
||||
new_channel = REAL(dispatch_io_create_with_io)(type, io, q, new_h);
|
||||
Block_release(new_h);
|
||||
return new_channel;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(void, dispatch_io_close, dispatch_io_t channel,
|
||||
dispatch_io_close_flags_t flags) {
|
||||
SCOPED_TSAN_INTERCEPTOR(dispatch_io_close, channel, flags);
|
||||
Release(thr, pc, (uptr)channel); // Acquire() in dispatch_io_create[_*].
|
||||
return REAL(dispatch_io_close)(channel, flags);
|
||||
}
|
||||
|
||||
} // namespace __tsan
|
||||
|
||||
#endif // SANITIZER_MAC
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// RUN: %clang_tsan %s -o %t -framework Foundation
|
||||
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
long my_global = 0;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
fprintf(stderr, "Hello world.\n");
|
||||
|
||||
dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
||||
|
||||
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
|
||||
|
||||
dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path.fileSystemRepresentation, O_CREAT | O_WRONLY,
|
||||
0666, queue, ^(int error) { });
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
NSData *ns_data = [NSMutableData dataWithLength:1000];
|
||||
dispatch_data_t data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
||||
|
||||
my_global++;
|
||||
dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
my_global++;
|
||||
dispatch_async(queue, ^{
|
||||
my_global++;
|
||||
if (done) {
|
||||
dispatch_semaphore_signal(sem);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
my_global++;
|
||||
dispatch_io_close(channel, 0);
|
||||
channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path.fileSystemRepresentation, O_RDONLY,
|
||||
0, queue, ^(int error) { });
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
my_global++;
|
||||
dispatch_io_read(channel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
my_global++;
|
||||
dispatch_async(queue, ^{
|
||||
my_global++;
|
||||
if (done) {
|
||||
dispatch_semaphore_signal(sem);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
my_global++;
|
||||
fprintf(stderr, "Done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Hello world.
|
||||
// CHECK-NOT: WARNING: ThreadSanitizer
|
||||
// CHECK: Done.
|
|
@ -0,0 +1,55 @@
|
|||
// RUN: %clang_tsan %s -o %t -framework Foundation
|
||||
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %deflake %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "../test.h"
|
||||
|
||||
dispatch_queue_t queue;
|
||||
dispatch_data_t data;
|
||||
dispatch_semaphore_t sem;
|
||||
const char *path;
|
||||
|
||||
long my_global = 0;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
fprintf(stderr, "Hello world.\n");
|
||||
print_address("addr=", 1, &my_global);
|
||||
barrier_init(&barrier, 2);
|
||||
|
||||
queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
sem = dispatch_semaphore_create(0);
|
||||
NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
|
||||
path = ns_path.fileSystemRepresentation;
|
||||
NSData *ns_data = [NSMutableData dataWithLength:1000];
|
||||
data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
||||
|
||||
dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
|
||||
if (! channel) abort();
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
if (error) abort();
|
||||
my_global = 42;
|
||||
barrier_wait(&barrier);
|
||||
});
|
||||
|
||||
dispatch_io_barrier(channel, ^{
|
||||
barrier_wait(&barrier);
|
||||
my_global = 43;
|
||||
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
dispatch_io_close(channel, 0);
|
||||
|
||||
fprintf(stderr, "Done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Hello world.
|
||||
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
|
||||
// CHECK: WARNING: ThreadSanitizer: data race
|
||||
// CHECK: Location is global 'my_global' {{(of size 8 )?}}at [[ADDR]] (gcd-io-barrier-race.mm.tmp+0x{{[0-9,a-f]+}})
|
||||
// CHECK: Done.
|
|
@ -0,0 +1,48 @@
|
|||
// RUN: %clang_tsan %s -o %t -framework Foundation
|
||||
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
dispatch_queue_t queue;
|
||||
dispatch_data_t data;
|
||||
dispatch_semaphore_t sem;
|
||||
const char *path;
|
||||
|
||||
long my_global = 0;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
fprintf(stderr, "Hello world.\n");
|
||||
|
||||
queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
sem = dispatch_semaphore_create(0);
|
||||
NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
|
||||
path = ns_path.fileSystemRepresentation;
|
||||
NSData *ns_data = [NSMutableData dataWithLength:1000];
|
||||
data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
||||
|
||||
dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
|
||||
if (! channel) abort();
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
dispatch_io_barrier(channel, ^{
|
||||
my_global = 42;
|
||||
});
|
||||
}
|
||||
|
||||
dispatch_io_barrier(channel, ^{
|
||||
my_global = 43;
|
||||
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
dispatch_io_close(channel, 0);
|
||||
|
||||
fprintf(stderr, "Done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Hello world.
|
||||
// CHECK-NOT: WARNING: ThreadSanitizer
|
||||
// CHECK: Done.
|
|
@ -0,0 +1,56 @@
|
|||
// RUN: %clang_tsan %s -o %t -framework Foundation
|
||||
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
long my_global = 0;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
fprintf(stderr, "Hello world.\n");
|
||||
|
||||
dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
||||
NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
|
||||
const char *path = ns_path.fileSystemRepresentation;
|
||||
dispatch_io_t channel;
|
||||
|
||||
dispatch_fd_t fd = open(path, O_CREAT | O_WRONLY, 0666);
|
||||
my_global++;
|
||||
channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
|
||||
my_global++;
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
if (! channel) abort();
|
||||
my_global++;
|
||||
dispatch_io_close(channel, 0);
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
|
||||
my_global++;
|
||||
channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) {
|
||||
my_global++;
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
if (! channel) abort();
|
||||
my_global++;
|
||||
dispatch_io_close(channel, 0);
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
|
||||
my_global++;
|
||||
dispatch_io_t other_channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
|
||||
channel = dispatch_io_create_with_io(DISPATCH_IO_STREAM, other_channel, queue, ^(int error) {
|
||||
my_global++;
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
if (! channel) abort();
|
||||
my_global++;
|
||||
dispatch_io_close(channel, 0);
|
||||
dispatch_io_close(other_channel, 0);
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
|
||||
fprintf(stderr, "Done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Hello world.
|
||||
// CHECK-NOT: WARNING: ThreadSanitizer
|
||||
// CHECK: Done.
|
|
@ -0,0 +1,54 @@
|
|||
// RUN: %clang_tsan %s -o %t -framework Foundation
|
||||
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %deflake %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "../test.h"
|
||||
|
||||
dispatch_queue_t queue;
|
||||
dispatch_data_t data;
|
||||
dispatch_semaphore_t sem;
|
||||
const char *path;
|
||||
|
||||
long my_global = 0;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
fprintf(stderr, "Hello world.\n");
|
||||
print_address("addr=", 1, &my_global);
|
||||
barrier_init(&barrier, 2);
|
||||
|
||||
queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
sem = dispatch_semaphore_create(0);
|
||||
NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
|
||||
path = ns_path.fileSystemRepresentation;
|
||||
NSData *ns_data = [NSMutableData dataWithLength:1000];
|
||||
data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
||||
|
||||
dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
|
||||
if (! channel) abort();
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
my_global = 42;
|
||||
barrier_wait(&barrier);
|
||||
});
|
||||
|
||||
dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
barrier_wait(&barrier);
|
||||
my_global = 42;
|
||||
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
dispatch_io_close(channel, 0);
|
||||
|
||||
fprintf(stderr, "Done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Hello world.
|
||||
// CHECK: addr=[[ADDR:0x[0-9,a-f]+]]
|
||||
// CHECK: WARNING: ThreadSanitizer: data race
|
||||
// CHECK: Location is global 'my_global' {{(of size 8 )?}}at [[ADDR]] (gcd-io-race.mm.tmp+0x{{[0-9,a-f]+}})
|
||||
// CHECK: Done.
|
|
@ -0,0 +1,117 @@
|
|||
// RUN: %clang_tsan %s -o %t -framework Foundation
|
||||
// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t 2>&1 | FileCheck %s
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
dispatch_queue_t queue;
|
||||
dispatch_data_t data;
|
||||
dispatch_semaphore_t sem;
|
||||
const char *path;
|
||||
|
||||
long my_global = 0;
|
||||
|
||||
void test_dispatch_io_write() {
|
||||
dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_CREAT | O_WRONLY, 0666, queue, ^(int error) { });
|
||||
if (! channel) abort();
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
my_global++;
|
||||
dispatch_io_write(channel, 0, data, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
if (error) abort();
|
||||
my_global++;
|
||||
dispatch_async(queue, ^{
|
||||
my_global++;
|
||||
if (done) {
|
||||
dispatch_semaphore_signal(sem);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
my_global++;
|
||||
dispatch_io_close(channel, 0);
|
||||
}
|
||||
|
||||
void test_dispatch_write() {
|
||||
dispatch_fd_t fd = open(path, O_CREAT | O_WRONLY, 0666);
|
||||
if (fd == -1) abort();
|
||||
|
||||
my_global++;
|
||||
dispatch_write(fd, data, queue, ^(dispatch_data_t data, int error) {
|
||||
if (error) abort();
|
||||
my_global++;
|
||||
dispatch_async(queue, ^{
|
||||
my_global++;
|
||||
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
my_global++;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void test_dispatch_io_read() {
|
||||
dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, path, O_RDONLY,
|
||||
0, queue, ^(int error) { });
|
||||
dispatch_io_set_high_water(channel, 1);
|
||||
|
||||
my_global++;
|
||||
dispatch_io_read(channel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t remainingData, int error) {
|
||||
if (error) abort();
|
||||
my_global++;
|
||||
dispatch_async(queue, ^{
|
||||
my_global++;
|
||||
if (done) {
|
||||
dispatch_semaphore_signal(sem);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
my_global++;
|
||||
dispatch_io_close(channel, 0);
|
||||
}
|
||||
|
||||
void test_dispatch_read() {
|
||||
dispatch_fd_t fd = open(path, O_RDONLY, 0);
|
||||
if (fd == -1) abort();
|
||||
|
||||
my_global++;
|
||||
dispatch_read(fd, SIZE_MAX, queue, ^(dispatch_data_t data, int error) {
|
||||
if (error) abort();
|
||||
my_global++;
|
||||
dispatch_async(queue, ^{
|
||||
my_global++;
|
||||
dispatch_semaphore_signal(sem);
|
||||
});
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
||||
my_global++;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
fprintf(stderr, "Hello world.\n");
|
||||
|
||||
queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_SERIAL);
|
||||
sem = dispatch_semaphore_create(0);
|
||||
NSString *ns_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp-gcd-io.%d", getpid()]];
|
||||
path = ns_path.fileSystemRepresentation;
|
||||
NSData *ns_data = [NSMutableData dataWithLength:1000];
|
||||
data = dispatch_data_create(ns_data.bytes, ns_data.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
||||
|
||||
test_dispatch_io_write();
|
||||
test_dispatch_write();
|
||||
test_dispatch_io_read();
|
||||
test_dispatch_read();
|
||||
|
||||
fprintf(stderr, "Done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Hello world.
|
||||
// CHECK-NOT: WARNING: ThreadSanitizer
|
||||
// CHECK: Done.
|
Loading…
Reference in New Issue