Allow dirty writes selectively

This commit is contained in:
Rodrigo Arias 2023-01-10 18:30:51 +01:00 committed by Rodrigo Arias Mallo
parent e1e0e9662d
commit 0944f84ac9
6 changed files with 263 additions and 55 deletions

View File

@ -106,7 +106,7 @@ bay_init(struct bay *bay)
static int static int
propagate_chan(struct bay_chan *bchan) propagate_chan(struct bay_chan *bchan)
{ {
dbg("propagating dirty channel %p\n", (void *) bchan); dbg("- propagating dirty channel %s\n", bchan->chan->name);
struct bay_cb *cur = NULL; struct bay_cb *cur = NULL;
struct bay_cb *tmp = NULL; struct bay_cb *tmp = NULL;
@ -123,7 +123,7 @@ propagate_chan(struct bay_chan *bchan)
int int
bay_propagate(struct bay *bay) bay_propagate(struct bay *bay)
{ {
dbg("propagating channels\n"); dbg("-- propagating channels begins\n");
struct bay_chan *cur = NULL; struct bay_chan *cur = NULL;
struct bay_chan *tmp = NULL; struct bay_chan *tmp = NULL;
DL_FOREACH_SAFE(bay->dirty, cur, tmp) { DL_FOREACH_SAFE(bay->dirty, cur, tmp) {
@ -134,8 +134,20 @@ bay_propagate(struct bay *bay)
} }
} }
/* Flush channels after running all the dirty callbacks, so we
* capture any potential double write when running the
* callbacks */
DL_FOREACH_SAFE(bay->dirty, cur, tmp) {
if (chan_flush(cur->chan) != 0) {
err("bay_propagate: chan_flush failed\n");
return -1;
}
}
bay->dirty = NULL; bay->dirty = NULL;
dbg("-- propagating channels ends\n");
return 0; return 0;
} }

View File

@ -36,6 +36,10 @@ static int
set_dirty(struct chan *chan) set_dirty(struct chan *chan)
{ {
if (chan->is_dirty) { if (chan->is_dirty) {
/* Already dirty and allowed, no need to do anything */
if (chan->allow_dirty_write)
return 0;
err("channel %s already dirty\n", chan->name); err("channel %s already dirty\n", chan->name);
return -1; return -1;
} }
@ -44,7 +48,8 @@ set_dirty(struct chan *chan)
if (chan->dirty_cb != NULL) { if (chan->dirty_cb != NULL) {
if (chan->dirty_cb(chan, chan->dirty_arg) != 0) { if (chan->dirty_cb(chan, chan->dirty_arg) != 0) {
err("dirty callback failed\n"); err("set_dirty %s: dirty callback failed\n",
chan->name);
return -1; return -1;
} }
} }
@ -60,7 +65,8 @@ check_duplicates(struct chan *chan, struct value *v)
// return 0; // return 0;
if (value_is_equal(&chan->last_value, v)) { if (value_is_equal(&chan->last_value, v)) {
err("check_duplicates: same value as last_value\n"); err("check_duplicates %s: same value as last_value\n",
chan->name);
return -1; return -1;
} }
@ -71,27 +77,30 @@ int
chan_set(struct chan *chan, struct value value) chan_set(struct chan *chan, struct value value)
{ {
if (chan->type != CHAN_SINGLE) { if (chan->type != CHAN_SINGLE) {
err("chan_set: cannot set on non-single channel\n"); err("chan_set %s: cannot set on non-single channel\n",
chan->name);
return -1; return -1;
} }
if (chan->is_dirty) { if (chan->is_dirty && !chan->allow_dirty_write) {
err("chan_set: cannot modify dirty channel\n"); err("chan_set %s: cannot modify dirty channel\n",
chan->name);
return -1; return -1;
} }
if (check_duplicates(chan, &value) != 0) { if (check_duplicates(chan, &value) != 0) {
err("chan_set: cannot set a duplicated value\n"); err("chan_set %s: cannot set a duplicated value\n",
chan->name);
return -1; return -1;
} }
char buf[128]; char buf[128];
dbg("chan_set: channel %p sets value %s\n", (void *) chan, dbg("chan_set %s: sets value to %s\n",
value_str(value, buf)); chan->name, value_str(value, buf));
chan->data.value = value; chan->data.value = value;
if (set_dirty(chan) != 0) { if (set_dirty(chan) != 0) {
err("chan_set: set_dirty failed\n"); err("chan_set %s: set_dirty failed\n", chan->name);
return -1; return -1;
} }
@ -108,31 +117,34 @@ int
chan_push(struct chan *chan, struct value value) chan_push(struct chan *chan, struct value value)
{ {
if (chan->type != CHAN_STACK) { if (chan->type != CHAN_STACK) {
err("chan_push: cannot push on non-stack channel\n"); err("chan_push %s: cannot push on non-stack channel\n",
chan->name);
return -1; return -1;
} }
if (chan->is_dirty) { if (chan->is_dirty && !chan->allow_dirty_write) {
err("chan_push: cannot modify dirty channel\n"); err("chan_push %s: cannot modify dirty channel\n",
chan->name);
return -1; return -1;
} }
if (check_duplicates(chan, &value) != 0) { if (check_duplicates(chan, &value) != 0) {
err("chan_push: cannot push a duplicated value\n"); err("chan_push %s: cannot push a duplicated value\n",
chan->name);
return -1; return -1;
} }
struct chan_stack *stack = &chan->data.stack; struct chan_stack *stack = &chan->data.stack;
if (stack->n >= MAX_CHAN_STACK) { if (stack->n >= MAX_CHAN_STACK) {
err("chan_push: channel stack full\n"); err("chan_push %s: channel stack full\n", chan->name);
abort(); return -1;
} }
stack->values[stack->n++] = value; stack->values[stack->n++] = value;
if (set_dirty(chan) != 0) { if (set_dirty(chan) != 0) {
err("chan_set: set_dirty failed\n"); err("chan_push %s: set_dirty failed\n", chan->name);
return -1; return -1;
} }
@ -150,34 +162,36 @@ int
chan_pop(struct chan *chan, struct value evalue) chan_pop(struct chan *chan, struct value evalue)
{ {
if (chan->type != CHAN_STACK) { if (chan->type != CHAN_STACK) {
err("chan_pop: cannot pop on non-stack channel\n"); err("chan_pop %s: cannot pop on non-stack channel\n",
chan->name);
return -1; return -1;
} }
if (chan->is_dirty) { if (chan->is_dirty && !chan->allow_dirty_write) {
err("chan_pop: cannot modify dirty channel\n"); err("chan_pop %s: cannot modify dirty channel\n",
chan->name);
return -1; return -1;
} }
struct chan_stack *stack = &chan->data.stack; struct chan_stack *stack = &chan->data.stack;
if (stack->n <= 0) { if (stack->n <= 0) {
err("chan_pop: channel stack empty\n"); err("chan_pop %s: channel stack empty\n", chan->name);
return -1; return -1;
} }
struct value *value = &stack->values[stack->n - 1]; struct value *value = &stack->values[stack->n - 1];
if (!value_is_equal(value, &evalue)) { if (!value_is_equal(value, &evalue)) {
err("chan_pop: unexpected value %ld (expected %ld)\n", err("chan_pop %s: unexpected value %ld (expected %ld)\n",
value->i, evalue.i); chan->name, value->i, evalue.i);
return -1; return -1;
} }
stack->n--; stack->n--;
if (set_dirty(chan) != 0) { if (set_dirty(chan) != 0) {
err("chan_set: set_dirty failed\n"); err("chan_pop %s: set_dirty failed\n", chan->name);
return -1; return -1;
} }
@ -194,7 +208,7 @@ get_value(struct chan *chan, struct value *value)
struct chan_stack *stack = &chan->data.stack; struct chan_stack *stack = &chan->data.stack;
if (stack->n <= 0) { if (stack->n <= 0) {
err("get_value: channel stack empty\n"); err("get_value %s: channel stack empty\n", chan->name);
return -1; return -1;
} }
@ -208,9 +222,29 @@ int
chan_read(struct chan *chan, struct value *value) chan_read(struct chan *chan, struct value *value)
{ {
if (get_value(chan, value) != 0) { if (get_value(chan, value) != 0) {
err("chan_read: get_value failed\n"); err("chan_read %s: get_value failed\n", chan->name);
return -1; return -1;
} }
return 0; return 0;
} }
/** Remove the dirty state */
int
chan_flush(struct chan *chan)
{
if (!chan->is_dirty) {
err("chan_flush %s: channel is not dirty\n", chan->name);
return -1;
}
chan->is_dirty = 0;
return 0;
}
void
chan_dirty_write(struct chan *chan, int allow)
{
chan->allow_dirty_write = allow;
}

View File

@ -35,6 +35,7 @@ struct chan {
enum chan_type type; enum chan_type type;
union chan_data data; union chan_data data;
int is_dirty; int is_dirty;
int allow_dirty_write;
struct value err_value; struct value err_value;
struct value last_value; struct value last_value;
chan_cb_t dirty_cb; chan_cb_t dirty_cb;
@ -51,6 +52,8 @@ int chan_push(struct chan *chan, struct value value);
int chan_pop(struct chan *chan, struct value expected); int chan_pop(struct chan *chan, struct value expected);
int chan_read(struct chan *chan, struct value *value); int chan_read(struct chan *chan, struct value *value);
enum chan_type chan_get_type(struct chan *chan); enum chan_type chan_get_type(struct chan *chan);
int chan_flush(struct chan *chan);
void chan_dirty_write(struct chan *chan, int allow);
/* Called when it becomes dirty */ /* Called when it becomes dirty */
void chan_set_dirty_cb(struct chan *chan, chan_cb_t func, void *arg); void chan_set_dirty_cb(struct chan *chan, chan_cb_t func, void *arg);

View File

@ -64,7 +64,8 @@ cb_select(struct chan *sel_chan, void *ptr)
return -1; return -1;
} }
dbg("selecting mux input %p\n", (void *) input); dbg("mux selects input key=%s chan=%s\n",
value_str(sel_value, buf), input->chan->name);
/* Set to null by default */ /* Set to null by default */
struct value out_value = value_null(); struct value out_value = value_null();
@ -102,8 +103,11 @@ cb_input(struct chan *in_chan, void *ptr)
} }
/* Nothing to do, the input is not selected */ /* Nothing to do, the input is not selected */
if (input == NULL || input->chan != in_chan) if (input == NULL || input->chan != in_chan) {
dbg("mux: input channel %s changed but not selected\n",
in_chan->name);
return 0; return 0;
}
dbg("selected mux input %s changed\n", in_chan->name); dbg("selected mux input %s changed\n", in_chan->name);
@ -135,6 +139,11 @@ mux_init(struct mux *mux,
return -1; return -1;
} }
/* The output channel must accept multiple writes in the same
* propagation phase, as we may write to the input and select
* channel at the same time. */
chan_dirty_write(output, 1);
memset(mux, 0, sizeof(struct mux_input)); memset(mux, 0, sizeof(struct mux_input));
mux->select = select; mux->select = select;
mux->output = output; mux->output = output;

View File

@ -2,7 +2,46 @@
#include "common.h" #include "common.h"
static void static void
check_single(void) test_dirty(void)
{
struct chan chan;
chan_init(&chan, CHAN_SINGLE, "testchan");
if (chan_set(&chan, value_int64(1)) != 0)
die("chan_set failed\n");
/* Now channel is dirty */
if (chan_set(&chan, value_int64(2)) == 0)
die("chan_set didn't fail\n");
if (chan_flush(&chan) != 0)
die("chan_flush failed\n");
chan_dirty_write(&chan, 1);
if (chan_set(&chan, value_int64(3)) != 0)
die("chan_set failed\n");
/* Now is dirty, but it should allow multiple writes */
if (chan_set(&chan, value_int64(4)) != 0)
die("chan_set failed\n");
struct value value;
struct value four = value_int64(4);
if (chan_read(&chan, &value) != 0)
die("chan_read failed\n");
if (!value_is_equal(&value, &four))
die("chan_read returned unexpected value\n");
if (chan_flush(&chan) != 0)
die("chan_flush failed\n");
}
static void
test_single(void)
{ {
struct chan chan; struct chan chan;
struct value one = { .type = VALUE_INT64, .i = 1 }; struct value one = { .type = VALUE_INT64, .i = 1 };
@ -33,7 +72,7 @@ check_single(void)
} }
//static void //static void
//check_stack(void) //test_stack(void)
//{ //{
// struct chan chan; // struct chan chan;
// //
@ -91,7 +130,8 @@ check_single(void)
int main(void) int main(void)
{ {
check_single(); test_single();
//check_stack(); test_dirty();
//test_stack();
return 0; return 0;
} }

View File

@ -1,7 +1,7 @@
#include "emu/mux.h" #include "emu/mux.h"
#include "common.h" #include "common.h"
#define N 4 #define N 6
//static int //static int
//select_active_thread(struct mux *mux, //select_active_thread(struct mux *mux,
@ -39,6 +39,132 @@
// return 0; // return 0;
//} //}
static void
check_output(struct mux *mux, struct value expected)
{
struct value out_value = value_null();
if (chan_read(mux->output, &out_value) != 0)
die("chan_read() failed for output channel\n");
if (!value_is_equal(&out_value, &expected)) {
char buf1[128];
char buf2[128];
die("unexpected value found %s in output (expected %s)\n",
value_str(out_value, buf1),
value_str(expected, buf2));
}
err("----- output ok -----\n");
}
static void
test_select(struct mux *mux, int key)
{
if (chan_set(mux->select, value_int64(key)) != 0)
die("chan_set failed\n");
if (bay_propagate(mux->bay) != 0)
die("bay_propagate failed\n");
check_output(mux, value_int64(1000 + key));
}
static void
test_input(struct mux *mux, int key)
{
/* Set the select channel to the selected key */
test_select(mux, key);
int new_value = 2000 + key;
/* Then change that channel */
struct mux_input *mi = mux_find_input(mux, value_int64(key));
if (mi == NULL)
die("mux_find_input failed to locate input %d\n", key);
if (chan_set(mi->chan, value_int64(new_value)) != 0)
die("chan_set failed\n");
if (bay_propagate(mux->bay) != 0)
die("bay_propagate failed\n");
check_output(mux, value_int64(new_value));
}
static void
test_select_and_input(struct mux *mux, int key)
{
/* Set the select channel to the selected key, but don't
* propagate the changes yet */
if (chan_set(mux->select, value_int64(key)) != 0)
die("chan_set failed\n");
int new_value = 2000 + key;
/* Also change that channel */
struct mux_input *mi = mux_find_input(mux, value_int64(key));
if (mi == NULL)
die("mux_find_input failed to locate input %d\n", key);
if (chan_set(mi->chan, value_int64(new_value)) != 0)
die("chan_set failed\n");
/* Write twice to the output */
if (bay_propagate(mux->bay) != 0)
die("bay_propagate failed\n");
check_output(mux, value_int64(new_value));
}
static void
test_input_and_select(struct mux *mux, int key)
{
int new_value = 2000 + key;
/* First change the input */
struct mux_input *mi = mux_find_input(mux, value_int64(key));
if (mi == NULL)
die("mux_find_input failed to locate input %d\n", key);
if (chan_set(mi->chan, value_int64(new_value)) != 0)
die("chan_set failed\n");
/* Then change select */
if (chan_set(mux->select, value_int64(key)) != 0)
die("chan_set failed\n");
/* Write twice to the output */
if (bay_propagate(mux->bay) != 0)
die("bay_propagate failed\n");
check_output(mux, value_int64(new_value));
}
static void
test_mid_propagate(struct mux *mux, int key)
{
int new_value = 2000 + key;
struct mux_input *mi = mux_find_input(mux, value_int64(key));
if (mi == NULL)
die("mux_find_input failed to locate input %d\n", key);
if (chan_set(mi->chan, value_int64(new_value)) != 0)
die("chan_set failed\n");
if (bay_propagate(mux->bay) != 0)
die("bay_propagate failed\n");
if (chan_set(mux->select, value_int64(key)) != 0)
die("chan_set failed\n");
if (bay_propagate(mux->bay) != 0)
die("bay_propagate failed\n");
check_output(mux, value_int64(new_value));
}
int int
main(void) main(void)
{ {
@ -81,27 +207,11 @@ main(void)
if (bay_propagate(&bay) != 0) if (bay_propagate(&bay) != 0)
die("bay_propagate failed\n"); die("bay_propagate failed\n");
test_select(&mux, 1);
/* Change select channel */ test_input(&mux, 2);
if (chan_set(&select, value_int64(2)) != 0) test_select_and_input(&mux, 3);
die("chan_set failed\n"); test_input_and_select(&mux, 4);
test_mid_propagate(&mux, 5);
/* Propagate values and call the callbacks */
if (bay_propagate(&bay) != 0)
die("bay_propagate failed\n");
struct value out_value = value_null();
if (chan_read(&output, &out_value) != 0)
die("chan_read() failed for output channel\n");
struct value expected_value = value_int64(1002);
if (!value_is_equal(&out_value, &expected_value)) {
char buf1[128];
char buf2[128];
die("unexpected value found %s in output (expected %s)\n",
value_str(out_value, buf1),
value_str(expected_value, buf2));
}
err("OK\n"); err("OK\n");