Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/launch.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ main(int argc, char *argv[])
cur_arg++;
}
else if (strcmp(cur_arg[0], "--max-livelock-cycle-limit") == 0 ||
strcmp(cur_arg[0], "--ML") == 0) {
strcmp(cur_arg[0], "-lc") == 0) {
setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[1], 1);
char *endptr;
if (strtol(cur_arg[1], &endptr, 10) == 0 && endptr[0] != '\0') {
Expand All @@ -117,9 +117,9 @@ main(int argc, char *argv[])
}
cur_arg += 2;
}
else if (cur_arg[0][2] == 'M' && cur_arg[0][3] == 'L' &&
isdigit(cur_arg[0][4])) {
setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[0] + 4, 1);
else if (cur_arg[0][1] == 'l' && cur_arg[0][2] == 'c' &&
isdigit(cur_arg[0][3])) {
setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[0] + 3, 1);
Comment on lines +120 to +122

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject malformed -lc<num> values instead of silently truncating them.

The branch validates only the first suffix character, so -lc12x stores 12x; downstream strtoul(..., 10) then consumes the 12 prefix and silently applies the wrong limit. Validate that the entire suffix is numeric before calling setenv, ideally sharing the same validator with the separated form.

Proposed validation
 else if (cur_arg[0][1] == 'l' && cur_arg[0][2] == 'c' &&
       isdigit(cur_arg[0][3])) {
+      char *endptr;
+      strtol(cur_arg[0] + 3, &endptr, 10);
+      if (endptr[0] != '\0') {
+        fprintf(stderr, "%s: illegal value\n",
+                "--max-livelock-cycle-limit");
+        exit(1);
+      }
       setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[0] + 3, 1);
       cur_arg++;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (cur_arg[0][1] == 'l' && cur_arg[0][2] == 'c' &&
isdigit(cur_arg[0][3])) {
setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[0] + 3, 1);
else if (cur_arg[0][1] == 'l' && cur_arg[0][2] == 'c' &&
isdigit(cur_arg[0][3])) {
char *endptr;
strtol(cur_arg[0] + 3, &endptr, 10);
if (endptr[0] != '\0') {
fprintf(stderr, "%s: illegal value\n",
"--max-livelock-cycle-limit");
exit(1);
}
setenv(ENV_MAX_LIVELOCK_CYCLE_LIMIT, cur_arg[0] + 3, 1);
cur_arg++;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/launch.c` around lines 120 - 122, Update the inline -lc option handling
near ENV_MAX_LIVELOCK_CYCLE_LIMIT to validate the entire numeric suffix, not
just its first character, before calling setenv. Reuse the validator used by the
separated-form argument when available, and reject values such as -lc12x rather
than storing them.

cur_arg++;
}
else if (strcmp(cur_arg[0], "--check-forward-progress") == 0 ||
Expand Down Expand Up @@ -170,7 +170,7 @@ main(int argc, char *argv[])
" [--first-deadlock|--first|-f] (default)\n"
" [--all-deadlocks|--all|-a]\n"
" [--check-for-livelock|-l] (experimental)\n"
" [--max-livelock-cycle-limit|--ML <num>]\n"
" [--max-livelock-cycle-limit|-lc <num>]\n"
" (default num = %d)\n"
" [--continue-after-data-race] (Use with -q\n"
" to suppress data race detection logs\n"
Expand Down
48 changes: 48 additions & 0 deletions test/producer-consumer/000-README
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
The code for this producer-consumer example was derived from a homework
from an earlier year.
The rest of this note describes how this code was constructed.

[ NOTE: If you use McMini with this, please replace 'sem_' by 'mysem_' in
partial_solution.c and producer-consumer.c. Otherwise, McMini will
think that 'sem_' is the real thing, and debug on that. This code
has already replaced 'sem_' with 'mysem_'.
I've found that 'mcmini-gdb -m 30 ./producer-consumer'
suffices to show the bug. ]

Take the code from hw6 (producer-consumer.c and partial-solution.c),
and in partial-solution.c, replace the original 'block()' function by
the variation below:

void block(sem_t *sem, int blocking_count) {
while (1) {
pthread_mutex_lock( &(sem->mutex) );
// if someone just posted and the count is back to what it was
// before I called sem_wait and blocked:
if (sem->count == blocking_count + 1) {
pthread_mutex_unlock( &(sem->mutex) );
return;
}
pthread_mutex_unlock( &(sem->mutex) );
sleep(1); // sleep for a second, and check later if count >= 0.
}
}

Finally, you need to set the blocking_count somewhere in your code.
We will add it to sem_wait. In the original partial_solution.c:sem_wait()
WE HAD:
if (sem->count < 0) {
// We must release lock, or no one can ever call sem_post() for us.
pthread_mutex_unlock( &(sem->mutex) );
block(sem);

YOU MUST CHANGE IT TO:
if (sem->count < 0) {
// We must release lock, or no one can ever call sem_post() for us.
int blocking_count = sem->count; // This is why we will block.
pthread_mutex_unlock( &(sem->mutex) );
block(sem, blocking_count);

ALSO CHANGE:
void block(sem_t *sem); // Used by sem_wait()
TO:
void block(sem_t *sem, int blocking_count); // Used by sem_wait()
33 changes: 33 additions & 0 deletions test/producer-consumer/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FILE = producer-consumer

CC = gcc

CPPFLAGS += -I../..
CFLAGS += -g3 -O0
LDLIBS += -lpthread

run: $(FILE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make run actually execute the example.

run only builds $(FILE); make run never launches the producer-consumer program.

Proposed fix
 run: $(FILE)
+	./$(FILE)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: $(FILE)
run: $(FILE)
./$(FILE)
🧰 Tools
🪛 checkmake (0.3.2)

[warning] 9-9: Target "run" should be declared PHONY.

(phonydeclared)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/producer-consumer/Makefile` at line 9, Update the Makefile’s run target
so it executes the built $(FILE) binary after ensuring $(FILE) is built, rather
than only declaring it as a prerequisite. Preserve the existing build dependency
and use the project’s established executable invocation convention if available.


# Create executable from producer-consumer.c and partial-solution.c
$(FILE): $(FILE).c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ \
$(FILE).c partial-solution.c $(LDLIBS)
Comment on lines +12 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Track partial-solution.c as a build prerequisite.

The recipe compiles partial-solution.c, but the target only depends on producer-consumer.c. Changes to the semaphore implementation therefore leave the executable stale until a manual clean or forced rebuild.

Proposed fix
-$(FILE): $(FILE).c
+$(FILE): $(FILE).c partial-solution.c
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$(FILE): $(FILE).c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ \
$(FILE).c partial-solution.c $(LDLIBS)
$(FILE): $(FILE).c partial-solution.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ \
$(FILE).c partial-solution.c $(LDLIBS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/producer-consumer/Makefile` around lines 12 - 14, Update the $(FILE)
target prerequisites in the Makefile to include partial-solution.c alongside
$(FILE).c, while leaving the existing compilation recipe unchanged.


gdb: $(FILE)
gdb $(FILE)

vi:
vi $(FILE).c

emacs:
emacs $(FILE).c

clean:
rm -f $(FILE) a.out *~

# 'make' views $v as a make variable and expands $v into the value of v.
# By typing $$, make will reduce it to a single '$' and pass it to the shell.
# The shell will view $dir as a shell variable and expand it.
dist:
dir=`basename $$PWD`; cd ..; tar cvf $$dir.tar ./$$dir; gzip $$dir.tar
dir=`basename $$PWD`; ls -l ../$$dir.tar.gz
85 changes: 85 additions & 0 deletions test/producer-consumer/partial-solution.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Remember to compile as: gcc -c -pthread THIS_FILE
// This generates a .o file that can be linked to your test program,
// myhw.c, which will contain the 'main()' routine.

// The motivation for semaphores is the producer-consumer problem.
// See Chapter 31 of ostep.org -
// especially page 10: Figure 31.12: Adding Mutual Exclusion (Correctly)
// Note that in class, I preferred to write:
// pthread_mutex_lock(&mutex); ... pthread_mutex_unlock(&mutex);
// while the textbook prefers the following pattern that does the same thing:
// sem_t mutex; sem_wait(&mutex); ... sem_post(&mutex);

#include <unistd.h>
#include <pthread.h>

// Recall that the count for a semaphore has the following interpretation:
// If count >= 0, then it's the number of calls to sem_wait() to be allowed.
// If count < 0, then it's the number of threads waiting on this semaphore.
// I refer to this as a "consistency criterion". The count always tells
// you the number of threads that are blocked (that are waiting).
// So, sem_wait() essentially does: {count--; if (count < 0) block();}
struct sem {
int count;
pthread_mutex_t mutex;
};
typedef struct sem sem_t;
// Unlike Java, C/C++ require a semicolon to end the 'struct sem' statement.

int sem_init(sem_t *sem, int ignore, int init) {
sem->count = init;
// Anybody who wants to use sem->count should acquire this lock first.
pthread_mutex_init(&(sem->mutex), NULL); // See 'man pthread_mutex_init'
}
Comment on lines +29 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
set -eu
rg -nP '^\s*int\s+sem_init\s*\(' \
  test/producer-consumer/partial-solution.c \
  test/producer-consumer/producer-consumer.c

Repository: mcminickpt/mcmini

Length of output: 354


🏁 Script executed:

#!/bin/sh
set -eu
printf '--- partial-solution.c ---\n'
cat -n test/producer-consumer/p_partial-solution.c 2>/dev/null || cat -n test/producer-consumer/partial-solution.c | sed -n '1,80p'
printf '\n--- producer-consumer.c relevant lines ---\n'
cat -n test/producer-consumer/producer-consumer.c | sed -n '1,80p'
printf '\n--- header references ---\n'
rg -n 'sem_init|sem_t' test/producer-consumer -C 2

Repository: mcminickpt/mcmini

Length of output: 246


🏁 Script executed:

#!/bin/sh
set -eu
printf '%s\n' '--- partial-solution.c ---'
cat -n test/producer-consumer/partial-solution.c | sed -n '1,80p'
printf '%s\n' ''
printf '%s\n' '--- producer-consumer.c relevant lines ---'
cat -n test/producer-consumer/producer-consumer.c | sed -n '1,80p'
printf '%s\n' ''
printf '%s\n' '--- header references ---'
rg -n 'sem_init|sem_t' test/producer-consumer -C 2

Repository: mcminickpt/mcmini

Length of output: 12096


Fix sem_init()’s third parameter type.

producer-consumer.c declares the third argument as unsigned int value, but partial-solution.c defines it as int init. The definition must match the declaration from the same ABI contract, so sem->count is not incorrectly initialized by values outside the range expected by the caller.

Proposed fix
-int sem_init(sem_t *sem, int ignore, int init) {
+int sem_init(sem_t *sem, int ignore, unsigned int init) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int sem_init(sem_t *sem, int ignore, int init) {
sem->count = init;
// Anybody who wants to use sem->count should acquire this lock first.
pthread_mutex_init(&(sem->mutex), NULL); // See 'man pthread_mutex_init'
}
int sem_init(sem_t *sem, int ignore, unsigned int init) {
sem->count = init;
// Anybody who wants to use sem->count should acquire this lock first.
pthread_mutex_init(&(sem->mutex), NULL); // See 'man pthread_mutex_init'
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/producer-consumer/partial-solution.c` around lines 29 - 33, Update the
third parameter of sem_init in partial-solution.c from int to unsigned int so
its definition matches the producer-consumer.c declaration and preserves the
expected ABI contract when initializing sem->count.


int sem_post(sem_t *sem) {
pthread_mutex_lock( &(sem->mutex) );
sem->count += 1;
pthread_mutex_unlock( &(sem->mutex) );
}

void block(sem_t *sem); // Used by sem_wait()

int sem_wait(sem_t *sem) {
pthread_mutex_lock( &(sem->mutex) );
sem->count -= 1;
if (sem->count < 0) {
// We must release lock, or no one can ever call sem_post() for us.
pthread_mutex_unlock( &(sem->mutex) );
block(sem);
pthread_mutex_lock( &(sem->mutex) );
}
pthread_mutex_unlock( &(sem->mutex) );
}
Comment on lines +22 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rename the custom semaphore API before running under McMini.

The README says McMini requires mysem_*, but the implementation and harness still use sem_*; McMini will model the custom calls as real semaphore operations rather than expose the intended blocking bug.

  • test/producer-consumer/partial-solution.c#L22-L53: rename sem_t and sem_init/sem_wait/sem_post to a custom mysem_* API.
  • test/producer-consumer/producer-consumer.c#L36-L47: update the type and prototypes to the renamed API.
  • test/producer-consumer/producer-consumer.c#L97-L98: initialize the renamed semaphores.
  • test/producer-consumer/producer-consumer.c#L122-L127: update producer wait/post calls.
  • test/producer-consumer/producer-consumer.c#L136-L141: update consumer wait/post calls.
  • test/producer-consumer/000-README#L5-L8: correct the statement once the rename is applied.
📍 Affects 3 files
  • test/producer-consumer/partial-solution.c#L22-L53 (this comment)
  • test/producer-consumer/producer-consumer.c#L36-L47
  • test/producer-consumer/producer-consumer.c#L97-L98
  • test/producer-consumer/producer-consumer.c#L122-L127
  • test/producer-consumer/producer-consumer.c#L136-L141
  • test/producer-consumer/000-README#L5-L8
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/producer-consumer/partial-solution.c` around lines 22 - 53, Rename the
custom semaphore API from sem_* to mysem_* so McMini recognizes the intended
implementation. In test/producer-consumer/partial-solution.c:22-53, rename sem_t
and sem_init/sem_wait/sem_post; update the corresponding type declarations and
prototypes in test/producer-consumer/producer-consumer.c:36-47, initialization
at :97-98, producer calls at :122-127, and consumer calls at :136-141. Correct
the API statement in test/producer-consumer/000-README:5-8 to match the renamed
interface.


void block(sem_t *sem) {
int mycount;
while (1) {
pthread_mutex_lock( &(sem->mutex) );
mycount = sem->count;
if (mycount >= 0) {
pthread_mutex_unlock( &(sem->mutex) );
return;
}
pthread_mutex_unlock( &(sem->mutex) );
sleep(1); // sleep for a second, and check later if count >= 0.
}
}

// This is a partial solution.
// The above code works correctly when there are two threads,
// but when there are three or more threads, a bug will appear.
// The bug appears when more than one thread is blocked:
// e.g., two threads blocked, one thread ready to post.
// In this case, our consistency criterion tells us that 'count == -2'.
// Now, suppose some additional thread calls 'sem_post(&mysem)',
// causing the 'count' to change from -2 to -1.
// Ideally, we would want one of the two threads that is trapped
// inside 'block()' to return.
// Unfortunately, 'mycount' will now have the value '-1', and both
// of the two threads will conclude that they cannot return from 'blcck()'.
// So, the consistency criterion is violated: two threads blocked, count == -1
// The essence of the homework is _either_ to diagnose this bug, or else
// to fix this bug (depending on the homework spec).
// If the homework is to fix the bug, then you must make it work
// for correctly for arbitrarily many threads.
146 changes: 146 additions & 0 deletions test/producer-consumer/producer-consumer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// This is a partial implementation of producer-consumer. Fill in the rest.

// For debugging in GDB with threads: methodology 1
// (gdb) info threads
// (gdb) thread 2
// (gdb) # and so on for other threads
// (gdb) where # each thread has its own stack
// (gdb) frame 2 # to go to call frame 2

// For debugging in GDB: methodology 2
// (gdb) break consumer
// (gdb) run
// (gdb) print buf
// (gdb) next
// (gdb) print buf # and so on

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h> // Needed for pthread_mutex_lock(), etc.
#include "MCProgress.h"
// This many producer and this many consumer threads.
#define NUM_PROD_THREADS 3
#define NUM_CONS_THREADS 3

// You have to:
// 1. declare sem_t for your implementation
// 2. Add to 'main': sem_init, pthread_create, pthread_join
// 3. define push_buf() and take_from_buf()
// 4. link this with your semaphore.o (for your semaphore.c file),
// which should defines sem_init, sem_wait, sem_post.
// Note that 'partial_solution.c' is available for you to implement
// sem_init, sem_wait, sem_post well enough for 1 producer, 1 consumer.

struct sem {
int count;
pthread_mutex_t mutex;
};
typedef struct sem sem_t;

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);

sem_t sem_producer; // Should count number of empty slots available
sem_t sem_consumer; // Should count number of items in the buffer
// pthread_mutex_t mut_buf = PTHREAD_MUTEX_INITIALIZER; // Lock for anybody touching buf
pthread_mutex_t mut_buf; // Lock for anybody touching buf

pthread_t prod_thread[NUM_PROD_THREADS];
pthread_t cons_thread[NUM_CONS_THREADS];
void *producer(void *arg);
void *consumer(void *arg);

// =========================================
// Implement circular buffer 'buf'
#define BUF_EMPTY_ITEM (-1)
#define BUF_NUM_ITEMS 2
// #define TRACE
int buf[BUF_NUM_ITEMS];
int push_idx = 0;
int take_idx = 0;
void initialize_buf() {
int i;
for (i = 0; i < BUF_NUM_ITEMS; i++) {
buf[i] = BUF_EMPTY_ITEM;
}
}
void push_buf(int work_item) {
assert(buf[push_idx] == BUF_EMPTY_ITEM);
buf[push_idx] = work_item;
#ifdef TRACE
fprintf(stderr, "PUSH: buf[%d] = %d\n", push_idx, work_item);
#endif
push_idx = (push_idx+1) % BUF_NUM_ITEMS;
}
int take_from_buf() {
int work_item = buf[take_idx];
buf[take_idx] = BUF_EMPTY_ITEM;
#ifdef TRACE
fprintf(stderr, "TAKE: work_item:%d = buf[%d]\n", work_item, take_idx);
#endif
assert(work_item != BUF_EMPTY_ITEM);
take_idx = (take_idx+1) % BUF_NUM_ITEMS;
return work_item;
}

// =========================================
// Implement producer-consumer, using buffer 'buf'.

int main() {
// ... uses pthread_create to start producer and consumer
// You must add that.
initialize_buf(); // Initialize entries in buffer to '-1' for error checking.
pthread_mutex_init(&(mut_buf), NULL);
sem_init(&sem_producer, 0, BUF_NUM_ITEMS);
sem_init(&sem_consumer, 0, 0);
int i;
for (i = 0; i < NUM_PROD_THREADS; i++) {
pthread_create(&prod_thread[i], NULL, producer, NULL);
}
for (i = 0; i < NUM_CONS_THREADS; i++) {
pthread_create(&cons_thread[i], NULL, consumer, NULL);
}
for (i = 0; i < NUM_PROD_THREADS; i++) {
pthread_join(prod_thread[i], NULL);
}
for (i = 0; i < NUM_CONS_THREADS; i++) {
pthread_join(cons_thread[i], NULL);
}
// WARNING: the primary thread runs main(). When main exits, the primary
// thread exits, unless you call 'pthread_join()' to
// have 'main' wait on the other threads before exiting.
while (1); // Don't let the primary thread exit
}

void *producer(void *arg) {
int work_item = 1;
while (1) {
// sleep( rand() % 5 );
sem_wait(&sem_producer); // Wait for empty slots
pthread_mutex_lock(&mut_buf);
push_buf(work_item++); // inside critical section with mut_buf lock
MC_PROGRESS();
pthread_mutex_unlock(&mut_buf);
sem_post(&sem_consumer); // Tell the consumer there's a new work item
}
}

// Exactly the same, but the inverse:
void *consumer(void *arg) {
while (1) {
int work_item;
// sleep( rand() % 5 );
sem_wait(&sem_consumer);
pthread_mutex_lock(&mut_buf);
work_item = take_from_buf();
MC_PROGRESS();
pthread_mutex_unlock(&mut_buf);
sem_post(&sem_producer);

printf("%d ", work_item);
fflush(stdout); // Force printing now; don't wait for the newline
}
}