FreeBSD, MySQL and the story of the unlinked named pipe


Don't do this at home, kids

August 2015.
The FreeBSD logo MySQL's logo

Introduction


One of my applications was using named pipes to feed data to a MySQL server while it was still being downloaded from a FTP server. One of those processes crashed, and MySQL was still waiting from data from a FIFO file that was deleted.

How to reproduce the situation


Create a table:

mysql> CREATE TABLE t (c char(20) DEFAULT NULL);

Create a named pipe on the system:

# mkfifo /tmp/ghost.fifo
# chown mysql /tmp/ghost.fifo

Load data from the named pipe:

mysql> LOAD DATA INFILE '/tmp/ghost.fifo' INTO TABLE t;

The request is now waiting for a writer to write something into the pipe.

Remove the file:

# rm /tmp/ghost.fifo

Now no-one can write anything into the pipe, since it isn't linked anywhere on the filesystem.

Investigating the problem


Determining what MySQL is doing


Now let's say you don't know what causes the problem. Your MySQL server is not behaving correctly. Requests using table t are waiting indefinitely to lock the table and nothing happens.

Let's check the process list:

mysql> show full processlist\G
*************************** 1. row ***************************
Id: 1
User: root
Host: localhost
db: fifo
Command: Query
Time: 13
State: System lock
Info: LOAD DATA INFILE '/tmp/ghost.fifo' INTO TABLE t
1 rows in set (0.00 sec)

Our request is indeed waiting for the system to unlock its thread.

Let's get more information in the InnoDB status report:

mysql> show engine innodb status\G
[...]
------------
TRANSACTIONS
------------
Trx id counter 2319
Purge done for trx's n:o < 2313 undo n:o < 0 state: running but idle
History list length 3
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0, not started
mysql tables in use 1, locked 1
MySQL thread id 1, OS thread handle 0x829e76000, query id 9 localhost root System lock
LOAD DATA INFILE '/tmp/ghost.fifo' INTO TABLE t
[...]

That gives us the thread handle of the request.

Let's create a dump of the server's memory, and see what it's doing:

# gcore -c mysqld.core `pgrep mysqld`

Now, let's find out what that "OS thread handle" is.
The line is printed in method thd_security_context in sql/sql_class.cc

extern "C"
char *thd_security_context(THD *thd, char *buffer, unsigned int length,
unsigned int max_query_len) {
[...]

len= my_snprintf(header, sizeof(header),
"MySQL thread id %lu, OS thread handle 0x%lx, query id %lu",
thd->thread_id, (ulong) thd->real_id, (ulong) thd->query_id);
str.length(0);
str.append(header, len);

[...]
}

Class THD's definition in sql/sql_class.h tells us that this attribute is a pointer to a pthread_t struct.


class THD :public MDL_context_owner,
public Statement,
public Open_tables_state
{
[...]
public:
pthread_t real_id; /* For debugging */
[...]
}

That struct is typedefed in FreeBSD's sys/_pthreadtypes.h and defined in lib/libthr/thread/thr_private.h:

typedef struct pthread *pthread_t;


/*
* Thread structure.
*/
struct pthread {
#define _pthread_startzero tid
/* Kernel thread id. */
long tid;
#define TID_TERMINATED 1
[...]
}

The interesting information here is that the system thread id is the first element of the struct. Let's fetch that from the dump using the thread handle's memory address:

If you don't have gdb installed:

make -C /usr/ports/devel/gdb install clean

Then:

# gdb /usr/local/libexec/mysqld mysqld.core
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "amd64-marcel-freebsd"...(no debugging symbols found)...
Core was generated by `mysqld'.
[...]
#0 0x00000008020b689c in __error () from /lib/libthr.so.3
[New Thread 829e76c00 (LWP 100474/mysqld)]
[...]
[New Thread 802c06400 (LWP 100411/mysqld)]
(gdb) x 0x829e76000
0x829e76000: 0x0001886c

0x00018879 = 100460

Let's see if we have a thread with that id in the process:

# procstat -t `pgrep mysqld` | grep 100460
1158 100460 mysqld - 0 120 sleep fifoor

# ps -U mysql -H -O wchan | grep fifoor
1158 fifoor - I 0:00.03 /usr/local/libexec/mysqld --defaults-extra-file=/var/db/mysql/my.cnf --basedir=/usr/local --datadir=/var/db/mysql --plu

We do. Let's see in the dump what it's doing:


(gdb) info threads
23 Thread 802c06400 (LWP 100411/mysqld) 0x000000080239453a in poll () from /lib/libc.so.7
22 Thread 802c06800 (LWP 100413/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
21 Thread 802c07800 (LWP 100414/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
20 Thread 802c07c00 (LWP 100415/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
19 Thread 802c08000 (LWP 100416/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
18 Thread 802c08400 (LWP 100417/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
17 Thread 802c08800 (LWP 100418/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
16 Thread 802c08c00 (LWP 100419/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
15 Thread 802c09000 (LWP 100420/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
14 Thread 802c09400 (LWP 100421/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
13 Thread 802c09800 (LWP 100422/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
12 Thread 802c0a800 (LWP 100425/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
11 Thread 802c0ac00 (LWP 100426/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
10 Thread 802c0b000 (LWP 100427/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
9 Thread 802c0b400 (LWP 100428/mysqld) 0x00000008023fc06a in select () from /lib/libc.so.7
8 Thread 802c0b800 (LWP 100429/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
7 Thread 802c0bc00 (LWP 100430/mysqld) 0x00000008023fc06a in select () from /lib/libc.so.7
6 Thread 802c0c000 (LWP 100431/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
5 Thread 802c0c400 (LWP 100432/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
4 Thread 802c0c800 (LWP 100433/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3
3 Thread 802c0d400 (LWP 100434/mysqld) 0x000000080233162a in _sigwait () from /lib/libc.so.7
2 Thread 829e76000 (LWP 100460/mysqld) 0x00000008023f492a in open () from /lib/libc.so.7
* 1 Thread 829e76800 (LWP 100461/mysqld) 0x00000008020b689c in __error () from /lib/libthr.so.3

(gdb) thread 2
[Switching to thread 2 (Thread 829e76000 (LWP 100460/mysqld))]#0 0x00000008023f492a in open () from /lib/libc.so.7
(gdb) bt
#0 0x00000008023f492a in open () from /lib/libc.so.7
#1 0x00000008020ad715 in open () from /lib/libthr.so.3
#2 0x000000000088ee7d in my_open ()
#3 0x00000000007f35aa in mysql_load ()
#4 0x00000000006c7d04 in mysql_execute_command ()
#5 0x00000000006c5c55 in mysql_parse ()
#6 0x00000000006c4303 in dispatch_command ()
#7 0x00000000006c57dd in do_command ()
#8 0x00000000006a2aa6 in do_handle_one_connection ()
#9 0x00000000006a2909 in handle_one_connection ()
#10 0x0000000000a67c01 in pfs_spawn_thread ()
#11 0x00000008020ab4a4 in pthread_create () from /lib/libthr.so.3
#12 0x0000000000000000 in ?? ()
(gdb) frame 2
#2 0x000000000088ee7d in my_open ()

Let's see what my_open is all about:

mysys/my_open.c:

File my_open(const char *FileName, int Flags, myf MyFlags) {
[...]

It's a wrapper to the different compatible systems open functions.

Let's see the value of the first argument:

(gdb) info registers
rax 0x5 5
rbx 0x7ffffd1a3c80 140737439743104
rcx 0xa6a650 10921552
rdx 0x0 0
rsi 0x0 0
rdi 0x7ffffd1a3c80 140737439743104
rbp 0x7ffffd1a3710 0x7ffffd1a3710
rsp 0x7ffffd1a3700 0x7ffffd1a3700
r8 0x7ffffd1a3c30 140737439743024
r9 0x0 0
r10 0x0 0
r11 0x828140e68 35032141416
r12 0x828b0e000 35042418688
r13 0x1 1
r14 0x10 16
r15 0x14 20
rip 0x88ee7d 0x88ee7d <my_open+29>
eflags 0x246 582
cs 0x43 67
ss 0x3b 59
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) x/s 0x7ffffd1a3c80
0x7ffffd1a3c80: "/tmp/ghost.fifo"

Yep, that's the one.

All this means that the process tried to open the named pipe, and that the thread is waiting for something in the kernel. This is confirmed by the fact that procstat told us before that the thread was waiting on a wait event called "fifoor".

Since the code is stuck in open, the file descriptor that will be returned is not created yet. That's why no fifo are present when you use procstat to list the process' open files.

Determining what the kernel is doing


Let's fire a kernel debugger and find our thread:

# kgdb
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "amd64-marcel-freebsd"...
[...]
#0 sched_switch (td=0xffffffff81641ff0, newtd=<value optimized out> flags=<value optimized out>) at /usr/src/sys/kern/sched_ule.c:1945
1945 cpuid = PCPU_GET(cpuid);
(kgdb) info threads
287 Thread 100444 (PID=10358: kgdb) sched_switch (td=0xfffff8004ca59920, newtd=<value optimized out>, flags=<value optimized out>)
at /usr/src/sys/kern/sched_ule.c:1945
[...]
273 Thread 100460 (PID=1158: mysqld) sched_switch (td=0xfffff8003b8a6920, newtd=<value optimized out>, flags=<value optimized out>)
at /usr/src/sys/kern/sched_ule.c:1945
[...]
Current language: auto; currently minimal

(kgdb) thread 273
[Switching to thread 273 (Thread 100460)]#0 sched_switch (td=0xfffff8003b8a6920, newtd=<value optimized out>, flags=<value optimized out>)
at /usr/src/sys/kern/sched_ule.c:1945
1945 cpuid = PCPU_GET(cpuid);

Let's see what's happening:

(kgdb) bt
#0 sched_switch (td=0xfffff8003b8a6920, newtd=<value optimized out>, flags=<value optimized out>) at /usr/src/sys/kern/sched_ule.c:1945
#1 0xffffffff80931621 in mi_switch (flags=260, newtd=0x0) at /usr/src/sys/kern/kern_synch.c:494
#2 0xffffffff8096e7eb in sleepq_catch_signals (wchan=0xfffff800376922c8, pri=104) at /usr/src/sys/kern/subr_sleepqueue.c:426
#3 0xffffffff8096e69f in sleepq_wait_sig (wchan=0x0, pri=0) at /usr/src/sys/kern/subr_sleepqueue.c:631
#4 0xffffffff8093103d in _sleep (ident=<value optimized out>, lock=<value optimized out>, priority=<value optimized out>,
wmesg=<value optimized out>, sbt=<value optimized out>, pr=<value optimized out>, flags=<value optimized out>)
at /usr/src/sys/kern/kern_synch.c:254
#5 0xffffffff808190aa in fifo_open (ap=0xfffffe0095f17678) at /usr/src/sys/fs/fifofs/fifo_vnops.c:191
#6 0xffffffff80e41fc1 in VOP_OPEN_APV (vop=<value optimized out>, a=<value optimized out>) at vnode_if.c:469
#7 0xffffffff809d6b54 in vn_open_vnode (vp=0xfffff8002e88e1d8, fmode=1, cred=0xfffff8004c80a000, td=0xfffff8003b8a6920, fp=0xfffff8003b63a3c0)
at vnode_if.h:196
#8 0xffffffff809d674c in vn_open_cred (ndp=0xfffffe0095f17880, flagp=0xfffffe0095f1795c, cmode=<value optimized out>,
vn_open_flags=<value optimized out>, cred=0x0, fp=0xfffff8003b63a3c0) at /usr/src/sys/kern/vfs_vnops.c:256
#9 0xffffffff809cfedf in kern_openat (td=0xfffff8003b8a6920, fd=-100, path=0x7ffffd1a3c80 <Error reading address 0x7ffffd1a3c80: Bad address>,
pathseg=UIO_USERSPACE, flags=1, mode=<value optimized out>) at /usr/src/sys/kern/vfs_syscalls.c:1096
#10 0xffffffff80d25841 in amd64_syscall (td=0xfffff8003b8a6920, traced=0) at subr_syscall.c:134
#11 0xffffffff80d0aa5b in Xfast_syscall () at /usr/src/sys/amd64/amd64/exception.S:391
#12 0x00000008023f492a in ?? ()
Previous frame inner to this frame (corrupt stack?)

(kgdb) frame 5
#5 0xffffffff808190aa in fifo_open (ap=0xfffffe0095f17678) at /usr/src/sys/fs/fifofs/fifo_vnops.c:191
191 error = msleep(&fip->fi_readers, PIPE_MTX(fpipe),

Reading the code of fs/fifofs/fifo_vnops.c tells us that unless another thread opens the pipe with the intention of writing into it, the thread will sleep eternally.

struct fifoinfo {
struct pipe *fi_pipe;
long fi_readers;
long fi_writers;
};

[...]

static int
fifo_open(ap)
struct vop_open_args /* {
struct vnode *a_vp;
int a_mode;
struct ucred *a_cred;
struct thread *a_td;
struct file *a_fp;
} */ *ap;
{
[...]
error = msleep(&fip->fi_readers, PIPE_MTX(fpipe),
PDROP | PCATCH | PSOCK, "fifoor", 0);
[...]
}

This is bad. Since the named pipe file was unlinked, there's no way anybody will open it and write into it.

Also, since the thread is waiting, the MySQL server cannot even be restarted without being SIGKILL'd first. That's really not good.

"Solving" the problem


Re-linking the file


Let's try extracting the vnode from memory, and linking it to a new file, in the kernel. Then, it would be as if it had never been deleted.

I couldn't find any method to achieve this, so I wrote a kernel module:

mysql_fifo_test.c:

#include <sys/types.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/types.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/namei.h>
#include <sys/fcntl.h>
#include <sys/vnode.h>
#include <sys/buf.h>
#include <sys/capability.h>

int link_vnode_at(struct thread *td, struct vnode *vp, char *path);
void mysql_fifo_test_main_thread(void *p);

static struct mtx main_thread_mutex;
static struct thread *main_thread;

/* Most of this code was taken from kern_linkat (kern/vfs_syscalls.c)*/
int link_vnode_at(struct thread *td, struct vnode *vp, char *path) {
struct mount *mp;
struct nameidata nd;
cap_rights_t rights;
int error;

bwillwrite();

again:
if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0) {
vrele(vp);
return (error);
}
NDINIT_ATRIGHTS(&nd, CREATE, LOCKPARENT | SAVENAME | AUDITVNODE2,
UIO_SYSSPACE, path, AT_FDCWD, cap_rights_init(&rights, CAP_LINKAT), main_thread);
if ((error = namei(&nd)) == 0) {
if (nd.ni_vp != NULL) {
if (nd.ni_dvp == nd.ni_vp)
vrele(nd.ni_dvp);
else
vput(nd.ni_dvp);
vrele(nd.ni_vp);
error = EEXIST;
} else if ((error = vn_lock(vp, LK_EXCLUSIVE)) == 0) {
/*
* Check for cross-device links. No need to
* recheck vp->v_type, since it cannot change
* for non-doomed vnode.
*/
if (nd.ni_dvp->v_mount != vp->v_mount)
error = EXDEV;
if (error == 0)
error = VOP_LINK(nd.ni_dvp, vp, &nd.ni_cnd);
VOP_UNLOCK(vp, 0);
vput(nd.ni_dvp);
} else {
vput(nd.ni_dvp);
NDFREE(&nd, NDF_ONLY_PNBUF);
vrele(vp);
vn_finished_write(mp);
goto again;
}
NDFREE(&nd, NDF_ONLY_PNBUF);
}
vrele(vp);
vn_finished_write(mp);
return (error);
}

struct fifoinfo {
struct pipe *fi_pipe;
long fi_readers;
long fi_writers;
};

void mysql_fifo_test_main_thread(void *p) {
mtx_lock(&main_thread_mutex);

struct vop_open_args *ap;
ap = (struct vop_open_args *) 0xfffffe0095e9a678;

struct vnode *vp = ap->a_vp;
vref(vp);

int error;
error = link_vnode_at(main_thread, vp, "/tmp/resurected.fifo");
printf("MySQL FIFO Test link_vnode_at returned %d.\n", error);

mtx_unlock(&main_thread_mutex);
kthread_exit();
}

static void
mysql_fifo_test_load() {
mtx_init(&main_thread_mutex, "mysql_fifo_test_main_thread_mutex", NULL, MTX_DEF);
mtx_lock(&main_thread_mutex);

kthread_add(mysql_fifo_test_main_thread, NULL, NULL, &main_thread, 0, 0, "mysql_fifo_test_main_thread");

mtx_unlock(&main_thread_mutex);
}

static void
mysql_fifo_test_unload() {
mtx_lock(&main_thread_mutex);
mtx_destroy(&main_thread_mutex);
}

static int
mysql_fifo_test_loader(struct module *m, int what, void *arg)
{
int err = 0;

switch (what) {
case MOD_LOAD:
mysql_fifo_test_load();
uprintf("MySQL FIFO Test loaded.\n");
break;
case MOD_UNLOAD:
mysql_fifo_test_unload();
uprintf("MySQL FIFO Test unloaded.\n");
break;
default:
err = EOPNOTSUPP;
break;
}
return(err);
}

static moduledata_t mysql_fifo_test_mod = {
"mysql_fifo_test",
mysql_fifo_test_loader,
NULL
};

DECLARE_MODULE(mysql_fifo_test, mysql_fifo_test_mod, SI_SUB_KLD, SI_ORDER_ANY);

Makefile:

KMOD = mysql_fifo_test
SRCS = mysql_fifo_test.c

# https://forums.freebsd.org/threads/vnode_if-h-missing-from-freebsd-9-0-beta1-0-thu-jul-28-16-34-16-utc-2011.27891/#post-230606
SRCS += vnode_if.h

.include <bsd.kmod.mk>

What this module only does, is that it creates a new thread, that will find a way to link the vnode (adressed directly from memory) to a new file.

Let's see if that works:

# kldload -v ./mysql_fifo_test.ko
MySQL FIFO Test KLD loaded.
Loaded ./mysql_fifo_test.ko, id=7

# kldunload -v ./mysql_fifo_test.ko
Unloading mysql_fifo_test.ko, id=7
MySQL FIFO Test unloaded.


# tail /var/log/messages
[...]
Aug 4 06:25:06 test kernel: MySQL FIFO Test link_vnode_at returned 2.

2 is NOENT. That means the filesystem driver did not accept to create a new link.

Let's read the drivers code and see where that happens.

With ZFS: in cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_dir.c:

/*
* Link zp into dl. Can only fail if zp has been unlinked.
*/
int
zfs_link_create(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag)
{
[...]
if (!(flag & ZRENAMING)) {
if (zp->z_unlinked) { /* no new links to unlinked zp */
ASSERT(!(flag & (ZNEW | ZEXISTS)));
mutex_exit(&zp->z_lock);
return (SET_ERROR(ENOENT));
}
[...]
}
[...]
}

With UFS: in ufs/ufs/ufs_vnops.c:

/*
* link vnode call
*/
static int
ufs_link(ap)
struct vop_link_args /* {
struct vnode *a_tdvp;
struct vnode *a_vp;
struct componentname *a_cnp;
} */ *ap;
{
[...]
/*
* The file may have been removed after namei droped the original
* lock.
*/
if (ip->i_effnlink == 0) {
error = ENOENT;
goto out;
}
[...]
}

Both ZFS and UFS prevent linking a file from an unlinked vnode in their source code.

That means this solution does not work.

Opening the fifo manually


If we can't link the vnode again so that someone could write into the pipe, let's be that someone.

Let's call fifo_open and fifo_close manually in the kernel module, using the arguments of the original call to fifo_open.

mysql_fifo_test.c:

#include <sys/types.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/types.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/namei.h>
#include <sys/fcntl.h>
#include <sys/vnode.h>
#include <sys/buf.h>
#include <sys/capability.h>

int link_vnode_at(struct thread *td, struct vnode *vp, char *path);
void mysql_fifo_test_main_thread(void *p);

static struct mtx main_thread_mutex;
static struct thread *main_thread;

void mysql_fifo_test_main_thread(void *p) {
struct vop_vector *f;
vop_open_t *fifo_open;
vop_close_t *fifo_close;

mtx_lock(&main_thread_mutex);
printf("MySQL FIFO Test In da thread.\n");

/* Get a pointer to the original open args */
struct vop_open_args *ap;
ap = (struct vop_open_args *) 0xfffffe0095f17678;

/* Get pointers to the fifo ops functions */
f = &fifo_specops;
fifo_open = f->vop_open;
fifo_close = f->vop_close;

/* Call fifo_open */
struct file some_file;
struct vop_open_args ap_open;
ap_open.a_vp = ap->a_vp;
ap_open.a_mode = FWRITE;
ap_open.a_cred = NULL; //unused in fifo_open
ap_open.a_fp = &some_file;
ap_open.a_td = main_thread;
fifo_open(&ap_open);

/* Call fifo_close */
struct vop_close_args ap_close;
ap_close.a_vp = ap->a_vp;
ap_close.a_fflag = FWRITE;
ap_close.a_cred = NULL; //unused in fifo_close
ap_close.a_td = main_thread;
fifo_close(&ap_close);

mtx_unlock(&main_thread_mutex);
kthread_exit();
}

static void
mysql_fifo_test_load() {
mtx_init(&main_thread_mutex, "mysql_fifo_test_main_thread_mutex", NULL, MTX_DEF);
mtx_lock(&main_thread_mutex);

kthread_add(mysql_fifo_test_main_thread, NULL, NULL, &main_thread, 0, 0, "mysql_fifo_test_main_thread");

mtx_unlock(&main_thread_mutex);
}

static void
mysql_fifo_test_unload() {
mtx_lock(&main_thread_mutex);
mtx_destroy(&main_thread_mutex);
}

static int
mysql_fifo_test_loader(struct module *m, int what, void *arg)
{
int err = 0;

switch (what) {
case MOD_LOAD:
mysql_fifo_test_load();
uprintf("MySQL FIFO Test loaded.\n");
break;
case MOD_UNLOAD:
mysql_fifo_test_unload();
uprintf("MySQL FIFO Test unloaded.\n");
break;
default:
err = EOPNOTSUPP;
break;
}
return(err);
}

static moduledata_t mysql_fifo_test_mod = {
"mysql_fifo_test",
mysql_fifo_test_loader,
NULL
};

DECLARE_MODULE(mysql_fifo_test, mysql_fifo_test_mod, SI_SUB_KLD, SI_ORDER_ANY);

Let's fire up the module:


# kldload -v ./mysql_fifo_test.ko
MySQL FIFO Test loaded.
Loaded ./mysql_fifo_test.ko, id=7

# kldunload -v ./mysql_fifo_test.ko
Unloading mysql_fifo_test.ko, id=7
MySQL FIFO Test unloaded.

Nothing crashed. That's good news. Let's look at the MySQL process:

mysql> LOAD DATA INFILE '/mnt/fbd/ghost.fifo' INTO TABLE t;
Query OK, 0 rows affected (1 hour 42 min 17.07 sec)
Records: 0 Deleted: 0 Skipped: 0 Warnings: 0

mysql>

Success! The thread lock is released, and MySQL can continue its normal life.

Conclusion


Moral of the story: don't unlink your named pipe files when someone is waiting for data to be written into them. If you do, you'll need to close them directly in the kernel to unlock your program threads, which is not something you'd like to do on a busy production server.