-
Notifications
You must be signed in to change notification settings - Fork 274
Add concurrency examples to regression tests #5521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add concurrency examples to regression tests #5521
Conversation
0314878
to
d12d57a
Compare
Codecov Report
@@ Coverage Diff @@
## develop #5521 +/- ##
========================================
Coverage 68.52% 68.52%
========================================
Files 1187 1187
Lines 98265 98265
========================================
Hits 67339 67339
Misses 30926 30926
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of code but it looks good to me,
Personally I am not a big fan of including *.c files though ;)
@@ -0,0 +1,29 @@ | |||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") | |||
set(gcc_only "-X gcc-only") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No quotes, this is the same bug fotis was struggling on windows with :)
If you add quotes here this'll be passed as a single argument to test.pl, i.e. test.pl "-X gcc only"
vs test.pl -X gcc-only
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually none of this is going to work on Windows as-is, because pthreads isn't natively supported on that platform. I'll either need to disable all these tests, or add a windows test-time dependency for a suitable compatibility layer.
|
||
add_test_pl_profile( | ||
"cbmc-paths-lifo" | ||
"$<TARGET_FILE:cbmc> --paths lifo ${gcc_only}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gcc_only needs to be in the next line, its an argument to test.pl not cbmc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed now.
|
||
add_test_pl_profile( | ||
"cbmc-cprover-smt2" | ||
"$<TARGET_FILE:cbmc> --cprover-smt2 ${gcc_only}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get changes on the CMakeLists.txt
. I think they are correctness related and pretty important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems good. Thanks for adding the tests first. Will you have these run with and without the sequentialisation? Also, I take it whoever is doing the sequentialisation is aware of the considerable amount of literature on "seqeuntialise and then run CBMC"?
enqueue(&input_queue, 8); | ||
enqueue(&input_queue, 9); | ||
enqueue(&input_queue, 0); | ||
join_thread(worker_thread1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this example and it should sequentialize well.
for(int i = 0; i < 100; ++i) | ||
{ | ||
int shared_count_copy = shared_count; | ||
// The following call to yield is here in order to cause a thread swap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wording of this sounds a bit certain. I can believe that 99.99% of the time it does but I don't think it is certain to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would feel better if the comment was reworded so that it captured this kind of "likely but not certain" but it is not a blocking issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'd make more sense to me as well, practically this should work but IMHO it’s probably not a good idea to be overly reliant on specific thread scheduling behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have reworded these comments slightly.
const int lock_error = pthread_mutex_lock(&mutex); | ||
assert(!lock_error); | ||
int shared_count_copy = shared_count; | ||
// The following call to yield is here in order to cause a thread swap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above.
a73d7d7
to
8c8ad9b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes. LGTM! 🎉
We have been looking into the existing literature and implementations. My thought is to run these specific tests with sequentialisation only, once it is implemented. This is because these test run up against existing limitations without the sequentialisation step. My thought is that the new workflow will look something like -
Therefore this directory will need some kind of |
c81c4bd
to
ff6080c
Compare
@thomasspriggs : Some thoughts
HTH |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not done reviewing yet just to get these out of the way:
} | ||
|
||
// Wait for signal. Note that pthreads requires us to lock and unlock the mutex | ||
// around the call to `pthread_cond_wait`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth pointing out if you want to clarify behavior unfamiliar with pthreads that in the period between calling pthread_cond_wait
and it returning the mutex is actually unlocked, just in case anyone is wondering if this doesn't block everything else.
{ | ||
const int lock_error = pthread_mutex_lock(&signal_mutex); | ||
assert(!lock_error); | ||
const int wait_error = pthread_cond_wait(&condition, &signal_mutex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Writing these comments as I go – note that POSIX requires an associated boolean condition with the condition variable, and correct wake-up behaviour should always be a loop that's waiting and checking the condition after the wait call. Even if the condition you want to wait on is just "someone called signal() at some point" this does, at minimum, still require a flag to operate correctly.
When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return from pthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.
Without this the expected result for this benchmark should actually be a failure, as a spurious wakeup is possible (this also means that actually we literally don't need to do anything to model signal
and wait
correctly other than unlocking and then locking the mutex...). Which to be clear isn't a bad thing, "there is a bug here and we can find it" is a more interesting result (and therefore test) than "we can't find anything here", but this should be documented as such so a future developer doesn't scratch their head why this test is failing despite everything seemingly working as intended.
for(int i = 0; i < 100; ++i) | ||
{ | ||
int shared_count_copy = shared_count; | ||
// The following call to yield is here in order to cause a thread swap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'd make more sense to me as well, practically this should work but IMHO it’s probably not a good idea to be overly reliant on specific thread scheduling behaviour.
{ | ||
for(int i = 0; i < 100; ++i) | ||
{ | ||
int shared_count_copy = shared_count; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be worth pointing out here that the odd construction with the copy-then-increment is because our intended approach (sequentialisation) will give us sequential consistency at the statement level, so an increment operation is going to be atomic for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple nitpicks here and there, nothing blocking I think.
@@ -0,0 +1,33 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do have to agree the util.c
is slightly sketchy, generally I prefer if tests stay self contained, even if that means duplicating some code. If you think it's worth it to give more emphasis to the parts that are actually different between tests we can leave it as is though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have removed util.c
and duplicated the code. I instinctively dislike cut and pasting code. But I am willing to accept that different ways of doing things may be better for test code.
|
||
sem_t global_semaphore; | ||
|
||
void *worker(void *arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ Unused function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
const pthread_attr_t *const attributes = NULL; | ||
void *const worker_argument = NULL; | ||
const int create_status = | ||
pthread_create(&worker_thread, attributes, &worker, worker_argument); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don’t like that this assumes the existence of a function with exactly this name, what's wrong with passing this in as an argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have now fixed this for the semaphores examples.
"enqueue begin:%lu end:%lu data:%d \n", queue->begin, queue->end, data); | ||
const int free_wait_error = sem_wait(&queue->free_space); | ||
assert(free_wait_error == false); | ||
queue->elements[queue->end] = data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓
Is the intention here that enqueue
and dequeue
are only allowed to be called from one thread respectively? Otherwise this would be a data race. If so, can you document this here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This queue implementation is only safe with a single enqueuing and dequeuing thread. I will add some comments to detail this limitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
@@ -0,0 +1,2 @@ | |||
#define NO_LOCKS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I don't like having tests split up over multiple files like this too much if we don't have to..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I have pushed a commit which duplicates this instead.
struct sharedt *shared = (struct sharedt *)arguments; | ||
for(int i = 0; i < 1000; ++i) | ||
{ | ||
#ifndef NO_LOCKS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these would be easier to follow if you had just copy&pasted the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -1,4 +1,4 @@ | |||
FUTURE | |||
FUTURE requires_posix_only_headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be careful with naming like this, AFAIK Windows is (or at least has been at some point) POSIX compliant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A standard install of Visual Studio does not make the POSIX header files we need available as part of its include path. Without these the build environment is does not have the POSIX compliance level we need. The windows API calls for threading are available through separate header and will have different naming conventions entirely. If a Windows based build environment were to be setup with the appropriate POSIX header files made available, then it should be possible to make these tests work on Windows.
Technically I believe these these would need a minimum of POSIX.1b for semaphores and POSIX.1c for thread creation and joining. The original POSIX.1 version would not be sufficient. But version POSIX.1-2001 onward would have everything we need.
5275a64
to
ddc71fe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for indulging my nitpicks here
This directory of regression tests is for examples which we intend to use for testing the future work on the analysis of concurrent programs based on a sequentialization approach. Tests which require POSIX specific headers will be disabled for the moment because these are not natively available on Windows. A windows POSIX compatability layer library could potentially be used to enable them to be run on Windows. However the work to do so has not been done.
So that each example can be read separately it its own right.
79f051d
to
52c0a80
Compare
This PR adds concurrency examples to the regression tests in preparation for the planned sequentialization work.