Re: [dm-devel] [PATCH v2 27/37] multipathd: watch bindings file with inotify + timestamp
On Thu, 2023-09-14 at 15:25 +0200, Martin Wilck wrote: > On Wed, 2023-09-13 at 17:07 -0500, Benjamin Marzinski wrote: > > > > I'm not sure if we should assert that the file has changed if we > > can't stat() it. > > I think it's better to (try to) reread the file than pretend that the > file hadn't changed ("if in doubt, reread"). Rationale: > > In general, the most likely cause for stat() to fail would be that > the > file (or the directory or file system containing it) had been > removed. > Actually, almost every possible error documented in stat(2) (except > ENOMEM) indicates such a condition in one way or the other. But in an > IN_MOVED_TO handler for just this file, that seems quite unlikely, so > we're really looking at a corner case situation here. A non-existing > file means no bindings; a "reconfigure" operation would cause > existing > bindings to be preserved, newly probed maps would get a new alias > assigned. Looking at it that way, "rereading" a non-existing file > doesn't do much harm. Our current bindings list may contain > additional > bindings that might be lost by re-reading, but still I think we have > to > assume that the file was intentionally deleted, and act accordingly. Ouch, I'm embarrassed. My own code does not what I described here, it returns an error if reading the bindings file fails, and does not replace the current global bindings with an empty list. We could discuss changing this behavior with a later patch set. Anyway, I think the basic reasoning was correct: we received an update event for the file and couldn't prove that our current internal state was up-to-date; thus we should try to re-read the file next time. Martin -- dm-devel mailing list dm-devel@redhat.com https://listman.redhat.com/mailman/listinfo/dm-devel
Re: [dm-devel] [PATCH v2 27/37] multipathd: watch bindings file with inotify + timestamp
On Thu, Sep 14, 2023 at 03:25:35PM +0200, Martin Wilck wrote: > On Wed, 2023-09-13 at 17:07 -0500, Benjamin Marzinski wrote: > > On Mon, Sep 11, 2023 at 06:38:36PM +0200, mwi...@suse.com wrote: > > > From: Martin Wilck > > > > > > Since "libmultipath: keep bindings in memory", we don't re-read the > > > bindings file after every modification. Add a notification > > > mechanism > > > that makes multipathd aware of changes to the bindings file. > > > Because > > > multipathd itself will change the bindings file, it must compare > > > timestamps in order to avoid reading the file repeatedly. > > > > > > Because select_alias() can be called from multiple thread contexts > > > (uxlsnr, > > > uevent handler), we need to add locking for the bindings file. The > > > timestamp must also be protected by a lock, because it can't be > > > read > > > or written atomically. > > > > > > Note: The notification mechanism expects the bindings file to be > > > atomically replaced by rename(2). Changes must be made in a > > > temporary file and > > > applied using rename(2), as in update_bindings_file(). The inotify > > > mechanism deliberately does not listen to close-after-write events > > > that would be generated by editing the bindings file directly. This > > > > > > Note also: new bindings will only be read from add_map_with_path(), > > > i.e. either during reconfigure(), or when a new map is created > > > during > > > runtime. Existing maps will not be renamed if the binding file > > > changes, > > > unless the user runs "multipathd reconfigure". This is not a change > > > wrt the previous code, but it should be mentioned anyway. > > > > > > Signed-off-by: Martin Wilck > > > > > > libmultipath: protect global_bindings with a mutex > > > > > > Signed-off-by: Martin Wilck > > > > > > libmultipath: check timestamp of bindings file before reading it > > > > > > Signed-off-by: Martin Wilck > > > --- > > > libmultipath/alias.c | 250 +- > > > > > > libmultipath/alias.h | 3 +- > > > libmultipath/libmultipath.version | 5 + > > > multipathd/uxlsnr.c | 36 - > > > tests/alias.c | 3 + > > > 5 files changed, 252 insertions(+), 45 deletions(-) > > > > > > diff --git a/libmultipath/alias.c b/libmultipath/alias.c > > > index 66e34e3..76ed62d 100644 > > > --- a/libmultipath/alias.c > > > +++ b/libmultipath/alias.c > > > @@ -10,6 +10,7 @@ > > > #include > > > #include > > > #include > > > +#include > > > > > > #include "debug.h" > > > #include "util.h" > > > @@ -22,6 +23,7 @@ > > > #include "config.h" > > > #include "devmapper.h" > > > #include "strbuf.h" > > > +#include "time-util.h" > > > > > > /* > > > * significant parts of this file were taken from iscsi-bindings.c > > > of the > > > @@ -50,6 +52,12 @@ > > > "# alias wwid\n" \ > > > "#\n" > > > > > > +/* uatomic access only */ > > > +static int bindings_file_changed = 1; > > > + > > > +static pthread_mutex_t timestamp_mutex = > > > PTHREAD_MUTEX_INITIALIZER; > > > +static struct timespec bindings_last_updated; > > > + > > > struct binding { > > > char *alias; > > > char *wwid; > > > @@ -60,6 +68,9 @@ struct binding { > > > * an abstract type. > > > */ > > > typedef struct _vector Bindings; > > > > I'm pretty sure that the global_bindings is only ever accessed with > > the > > vecs lock held, so it doesn't really need it's own lock. On the > > otherhand, I understand the desire to not keep adding things that the > > vecs lock is protecting, so I'm fine with this. > > I have to admit I didn't think about the vecs lock. I find it counter- > intuitive to think about the vecs lock as "global multipath lock", even > if it is in practice. In my mind, the vecs lock protects the pathvec > and the mpvec, and possibly their members, but no other data > structures. Implicitly relying on the vecs lock protecting unrelated > data structures seems unwise to me. Of course we have to consider the > possibility of deadlock, but that's avoided by holding the new lock > only in very short portions of the code, and not across any function > calls or the like. > > > > > > + > > > +/* Protect global_bindings */ > > > +static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER; > > > static Bindings global_bindings = { .allocated = 0 }; > > > > > > enum { > > > @@ -78,6 +89,26 @@ static void _free_binding(struct binding *bdg) > > > free(bdg); > > > } > > > > > > +static void free_bindings(Bindings *bindings) > > > +{ > > > + struct binding *bdg; > > > + int i; > > > + > > > + vector_foreach_slot(bindings, bdg, i) > > > + _free_binding(bdg); > > > + vector_reset(bindings); > > > +} > > > + > > > +static void set_global_bindings(Bindings *bindings) > > > +{ > > > > However, if we are acting like the vecs lock isn't protecting > > global_bindings, then we should move this to inside t
Re: [dm-devel] [PATCH v2 27/37] multipathd: watch bindings file with inotify + timestamp
On Wed, 2023-09-13 at 17:07 -0500, Benjamin Marzinski wrote: > On Mon, Sep 11, 2023 at 06:38:36PM +0200, mwi...@suse.com wrote: > > From: Martin Wilck > > > > Since "libmultipath: keep bindings in memory", we don't re-read the > > bindings file after every modification. Add a notification > > mechanism > > that makes multipathd aware of changes to the bindings file. > > Because > > multipathd itself will change the bindings file, it must compare > > timestamps in order to avoid reading the file repeatedly. > > > > Because select_alias() can be called from multiple thread contexts > > (uxlsnr, > > uevent handler), we need to add locking for the bindings file. The > > timestamp must also be protected by a lock, because it can't be > > read > > or written atomically. > > > > Note: The notification mechanism expects the bindings file to be > > atomically replaced by rename(2). Changes must be made in a > > temporary file and > > applied using rename(2), as in update_bindings_file(). The inotify > > mechanism deliberately does not listen to close-after-write events > > that would be generated by editing the bindings file directly. This > > > > Note also: new bindings will only be read from add_map_with_path(), > > i.e. either during reconfigure(), or when a new map is created > > during > > runtime. Existing maps will not be renamed if the binding file > > changes, > > unless the user runs "multipathd reconfigure". This is not a change > > wrt the previous code, but it should be mentioned anyway. > > > > Signed-off-by: Martin Wilck > > > > libmultipath: protect global_bindings with a mutex > > > > Signed-off-by: Martin Wilck > > > > libmultipath: check timestamp of bindings file before reading it > > > > Signed-off-by: Martin Wilck > > --- > > libmultipath/alias.c | 250 +- > > > > libmultipath/alias.h | 3 +- > > libmultipath/libmultipath.version | 5 + > > multipathd/uxlsnr.c | 36 - > > tests/alias.c | 3 + > > 5 files changed, 252 insertions(+), 45 deletions(-) > > > > diff --git a/libmultipath/alias.c b/libmultipath/alias.c > > index 66e34e3..76ed62d 100644 > > --- a/libmultipath/alias.c > > +++ b/libmultipath/alias.c > > @@ -10,6 +10,7 @@ > > #include > > #include > > #include > > +#include > > > > #include "debug.h" > > #include "util.h" > > @@ -22,6 +23,7 @@ > > #include "config.h" > > #include "devmapper.h" > > #include "strbuf.h" > > +#include "time-util.h" > > > > /* > > * significant parts of this file were taken from iscsi-bindings.c > > of the > > @@ -50,6 +52,12 @@ > > "# alias wwid\n" \ > > "#\n" > > > > +/* uatomic access only */ > > +static int bindings_file_changed = 1; > > + > > +static pthread_mutex_t timestamp_mutex = > > PTHREAD_MUTEX_INITIALIZER; > > +static struct timespec bindings_last_updated; > > + > > struct binding { > > char *alias; > > char *wwid; > > @@ -60,6 +68,9 @@ struct binding { > > * an abstract type. > > */ > > typedef struct _vector Bindings; > > I'm pretty sure that the global_bindings is only ever accessed with > the > vecs lock held, so it doesn't really need it's own lock. On the > otherhand, I understand the desire to not keep adding things that the > vecs lock is protecting, so I'm fine with this. I have to admit I didn't think about the vecs lock. I find it counter- intuitive to think about the vecs lock as "global multipath lock", even if it is in practice. In my mind, the vecs lock protects the pathvec and the mpvec, and possibly their members, but no other data structures. Implicitly relying on the vecs lock protecting unrelated data structures seems unwise to me. Of course we have to consider the possibility of deadlock, but that's avoided by holding the new lock only in very short portions of the code, and not across any function calls or the like. > > > + > > +/* Protect global_bindings */ > > +static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER; > > static Bindings global_bindings = { .allocated = 0 }; > > > > enum { > > @@ -78,6 +89,26 @@ static void _free_binding(struct binding *bdg) > > free(bdg); > > } > > > > +static void free_bindings(Bindings *bindings) > > +{ > > + struct binding *bdg; > > + int i; > > + > > + vector_foreach_slot(bindings, bdg, i) > > + _free_binding(bdg); > > + vector_reset(bindings); > > +} > > + > > +static void set_global_bindings(Bindings *bindings) > > +{ > > However, if we are acting like the vecs lock isn't protecting > global_bindings, then we should move this to inside the bindings > mutex below. > Otherwise, if it was possible for another thread to modify > global_bindings after setting old_bindings but before grabbing the > mutex, it could add a binding, making old_binding->allocated and > old_binding->slot incorrect, so we'd be freeing random memory below. > Right, thanks. > > + Binding
Re: [dm-devel] [PATCH v2 27/37] multipathd: watch bindings file with inotify + timestamp
On Mon, Sep 11, 2023 at 06:38:36PM +0200, mwi...@suse.com wrote: > From: Martin Wilck > > Since "libmultipath: keep bindings in memory", we don't re-read the > bindings file after every modification. Add a notification mechanism > that makes multipathd aware of changes to the bindings file. Because > multipathd itself will change the bindings file, it must compare > timestamps in order to avoid reading the file repeatedly. > > Because select_alias() can be called from multiple thread contexts (uxlsnr, > uevent handler), we need to add locking for the bindings file. The > timestamp must also be protected by a lock, because it can't be read > or written atomically. > > Note: The notification mechanism expects the bindings file to be > atomically replaced by rename(2). Changes must be made in a temporary file and > applied using rename(2), as in update_bindings_file(). The inotify > mechanism deliberately does not listen to close-after-write events > that would be generated by editing the bindings file directly. This > > Note also: new bindings will only be read from add_map_with_path(), > i.e. either during reconfigure(), or when a new map is created during > runtime. Existing maps will not be renamed if the binding file changes, > unless the user runs "multipathd reconfigure". This is not a change > wrt the previous code, but it should be mentioned anyway. > > Signed-off-by: Martin Wilck > > libmultipath: protect global_bindings with a mutex > > Signed-off-by: Martin Wilck > > libmultipath: check timestamp of bindings file before reading it > > Signed-off-by: Martin Wilck > --- > libmultipath/alias.c | 250 +- > libmultipath/alias.h | 3 +- > libmultipath/libmultipath.version | 5 + > multipathd/uxlsnr.c | 36 - > tests/alias.c | 3 + > 5 files changed, 252 insertions(+), 45 deletions(-) > > diff --git a/libmultipath/alias.c b/libmultipath/alias.c > index 66e34e3..76ed62d 100644 > --- a/libmultipath/alias.c > +++ b/libmultipath/alias.c > @@ -10,6 +10,7 @@ > #include > #include > #include > +#include > > #include "debug.h" > #include "util.h" > @@ -22,6 +23,7 @@ > #include "config.h" > #include "devmapper.h" > #include "strbuf.h" > +#include "time-util.h" > > /* > * significant parts of this file were taken from iscsi-bindings.c of the > @@ -50,6 +52,12 @@ > "# alias wwid\n" \ > "#\n" > > +/* uatomic access only */ > +static int bindings_file_changed = 1; > + > +static pthread_mutex_t timestamp_mutex = PTHREAD_MUTEX_INITIALIZER; > +static struct timespec bindings_last_updated; > + > struct binding { > char *alias; > char *wwid; > @@ -60,6 +68,9 @@ struct binding { > * an abstract type. > */ > typedef struct _vector Bindings; I'm pretty sure that the global_bindings is only ever accessed with the vecs lock held, so it doesn't really need it's own lock. On the otherhand, I understand the desire to not keep adding things that the vecs lock is protecting, so I'm fine with this. > + > +/* Protect global_bindings */ > +static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER; > static Bindings global_bindings = { .allocated = 0 }; > > enum { > @@ -78,6 +89,26 @@ static void _free_binding(struct binding *bdg) > free(bdg); > } > > +static void free_bindings(Bindings *bindings) > +{ > + struct binding *bdg; > + int i; > + > + vector_foreach_slot(bindings, bdg, i) > + _free_binding(bdg); > + vector_reset(bindings); > +} > + > +static void set_global_bindings(Bindings *bindings) > +{ However, if we are acting like the vecs lock isn't protecting global_bindings, then we should move this to inside the bindings mutex below. Otherwise, if it was possible for another thread to modify global_bindings after setting old_bindings but before grabbing the mutex, it could add a binding, making old_binding->allocated and old_binding->slot incorrect, so we'd be freeing random memory below. > + Bindings old_bindings = global_bindings; > + > + pthread_mutex_lock(&bindings_mutex); > + global_bindings = *bindings; > + pthread_mutex_unlock(&bindings_mutex); > + free_bindings(&old_bindings); > +} > + > static const struct binding *get_binding_for_alias(const Bindings *bindings, > const char *alias) > { > @@ -199,7 +230,8 @@ static int delete_binding(Bindings *bindings, const char > *wwid) > return BINDING_DELETED; > } > > -static int write_bindings_file(const Bindings *bindings, int fd) > +static int write_bindings_file(const Bindings *bindings, int fd, > +struct timespec *ts) > { > struct binding *bnd; > STRBUF_ON_STACK(content); > @@ -227,9 +259,56 @@ static int write_bindings_file(const Bindings *bindings, > int fd) > } > len -= n; > } > + fsync(fd); > + if (ts) { > +
[dm-devel] [PATCH v2 27/37] multipathd: watch bindings file with inotify + timestamp
From: Martin Wilck Since "libmultipath: keep bindings in memory", we don't re-read the bindings file after every modification. Add a notification mechanism that makes multipathd aware of changes to the bindings file. Because multipathd itself will change the bindings file, it must compare timestamps in order to avoid reading the file repeatedly. Because select_alias() can be called from multiple thread contexts (uxlsnr, uevent handler), we need to add locking for the bindings file. The timestamp must also be protected by a lock, because it can't be read or written atomically. Note: The notification mechanism expects the bindings file to be atomically replaced by rename(2). Changes must be made in a temporary file and applied using rename(2), as in update_bindings_file(). The inotify mechanism deliberately does not listen to close-after-write events that would be generated by editing the bindings file directly. This Note also: new bindings will only be read from add_map_with_path(), i.e. either during reconfigure(), or when a new map is created during runtime. Existing maps will not be renamed if the binding file changes, unless the user runs "multipathd reconfigure". This is not a change wrt the previous code, but it should be mentioned anyway. Signed-off-by: Martin Wilck libmultipath: protect global_bindings with a mutex Signed-off-by: Martin Wilck libmultipath: check timestamp of bindings file before reading it Signed-off-by: Martin Wilck --- libmultipath/alias.c | 250 +- libmultipath/alias.h | 3 +- libmultipath/libmultipath.version | 5 + multipathd/uxlsnr.c | 36 - tests/alias.c | 3 + 5 files changed, 252 insertions(+), 45 deletions(-) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index 66e34e3..76ed62d 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "debug.h" #include "util.h" @@ -22,6 +23,7 @@ #include "config.h" #include "devmapper.h" #include "strbuf.h" +#include "time-util.h" /* * significant parts of this file were taken from iscsi-bindings.c of the @@ -50,6 +52,12 @@ "# alias wwid\n" \ "#\n" +/* uatomic access only */ +static int bindings_file_changed = 1; + +static pthread_mutex_t timestamp_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct timespec bindings_last_updated; + struct binding { char *alias; char *wwid; @@ -60,6 +68,9 @@ struct binding { * an abstract type. */ typedef struct _vector Bindings; + +/* Protect global_bindings */ +static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER; static Bindings global_bindings = { .allocated = 0 }; enum { @@ -78,6 +89,26 @@ static void _free_binding(struct binding *bdg) free(bdg); } +static void free_bindings(Bindings *bindings) +{ + struct binding *bdg; + int i; + + vector_foreach_slot(bindings, bdg, i) + _free_binding(bdg); + vector_reset(bindings); +} + +static void set_global_bindings(Bindings *bindings) +{ + Bindings old_bindings = global_bindings; + + pthread_mutex_lock(&bindings_mutex); + global_bindings = *bindings; + pthread_mutex_unlock(&bindings_mutex); + free_bindings(&old_bindings); +} + static const struct binding *get_binding_for_alias(const Bindings *bindings, const char *alias) { @@ -199,7 +230,8 @@ static int delete_binding(Bindings *bindings, const char *wwid) return BINDING_DELETED; } -static int write_bindings_file(const Bindings *bindings, int fd) +static int write_bindings_file(const Bindings *bindings, int fd, + struct timespec *ts) { struct binding *bnd; STRBUF_ON_STACK(content); @@ -227,9 +259,56 @@ static int write_bindings_file(const Bindings *bindings, int fd) } len -= n; } + fsync(fd); + if (ts) { + struct stat st; + + if (fstat(fd, &st) == 0) + *ts = st.st_mtim; + else + clock_gettime(CLOCK_REALTIME_COARSE, ts); + } return 0; } +void handle_bindings_file_inotify(const struct inotify_event *event) +{ + struct config *conf; + const char *base; + bool changed = false; + struct stat st; + struct timespec ts = { 0 }; + int ret; + + if (!(event->mask & IN_MOVED_TO)) + return; + + conf = get_multipath_config(); + base = strrchr(conf->bindings_file, '/'); + changed = base && base > conf->bindings_file && + !strcmp(base + 1, event->name); + ret = stat(conf->bindings_file, &st); + put_multipath_config(conf); + + if (!changed) + return; + + pthread_mutex_lock(×tamp_mutex); + if (ret == 0) { +