Bug?: unlink cause btrfs error but other fs don't

From: Hongzhi, Song
Date: Wed Sep 04 2019 - 04:03:19 EST


Hi ,


*Kernel:*

ÂÂÂ After v5.2-rc1, qemux86-64

ÂÂÂ make -j40 ARCH=x86_64 CROSS_COMPILE=x86-64-gcc
ÂÂÂ use qemu to bootup kernel


*Reproduce:*

ÂÂÂ There is a test case failed on btrfs but success on other fs(ext4,ext3), see attachment.


ÂÂÂ Download attachments:

ÂÂÂ ÂÂÂ gcc test.c -o myout -Wall -lpthread

ÂÂÂ ÂÂÂ copy myout and run.sh to your qemu same directory.

ÂÂÂ ÂÂÂ on qemu:

ÂÂÂ ÂÂÂ ÂÂÂ ./run.sh


ÂÂÂ I found the block device size with btrfs set 512M will cause the error.
ÂÂÂ 256M and 1G all success.


*Error info:*

ÂÂÂ "BTRFS warning (device loop0): could not allocate space for a delete; will truncate on mount"


*Related patch:*

ÂÂÂ I use git bisect to find the following patch introduces the issue.

ÂÂÂ commit c8eaeac7b734347c3afba7008b7af62f37b9c140
ÂÂÂ Author: Josef Bacik <josef@xxxxxxxxxxxxxx>
ÂÂÂ Date:ÂÂ Wed Apr 10 15:56:10 2019 -0400

ÂÂÂÂÂÂÂ btrfs: reserve delalloc metadata differently
ÂÂÂÂÂÂÂ ...


Anyone's reply will be appreciated.

--Hongzhi

Attachment: run.sh
Description: application/shellscript

/**
* mount a 512M btrfs block device.
* and then fill the block device to the full with multi threads(ncpus+2).
* and then unlink the files in the block device with multi threads.
*
*/


#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <stdarg.h>
#include <unistd.h>
#include <dirent.h>
#include <grp.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>

//#define PATH_MAX 512

unsigned int nthreads;
static volatile int run;
struct worker *workers;
static int enospc_cnt; //no space count
const char *homepath;

struct worker {
char dir[PATH_MAX];
};

#define MIN(a, b) ({\
typeof(a) _a = (a); \
typeof(b) _b = (b); \
_a < _b ? _a : _b; \
})

#define pr_err(a) do{\
printf("%s:%d:\n", __FILE__, __LINE__); \
perror(a); \
}while(0)

unsigned int get_ncpus_conf()
{
unsigned int ncpus_conf = -1;

if ( (ncpus_conf = sysconf(_SC_NPROCESSORS_CONF)) == -1 ) {
pr_err("Fail to get ncpus!");
exit(1);
}

return ncpus_conf;
}

void fill_fs(const char *path)
{
int i = 0;
char file[PATH_MAX];
char buf[4096];
size_t len;
ssize_t ret;
int fd;

for (;;) {
len = random() % (1024 * 102400);

snprintf(file, sizeof(file), "%s/file%i", path, i++);
printf("%s \n", file);

fd = open(file, O_WRONLY | O_CREAT, 0700);
if (fd == -1) {
if (errno != ENOSPC)
pr_err("Fail to open()");
exit(1);

printf("No space to open() \n");
return;
}

while (len) {
ret = write(fd, buf, MIN(len, sizeof(buf)));

if (ret < 0) {
close(fd);

if (errno != ENOSPC)
pr_err("Fail to write()");

printf("No space to write() \n");
return;
}

len -= ret;
}

close(fd);
}
}

static void *worker(void *p)
{

struct worker *w = p;
DIR *d;
struct dirent *ent;
char file[2*PATH_MAX];

while (run) {
fill_fs(w->dir);

__atomic_fetch_add(&enospc_cnt, 1, __ATOMIC_SEQ_CST);

d = opendir(w->dir);
while ((ent = readdir(d))) {

if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
continue;

snprintf(file, sizeof(file), "%s/%s", w->dir, ent->d_name);

printf("Unlinking %s \n", file);

if( unlink(file) ) {
pr_err("Fail to unlink");
}
break;
}
closedir(d);
}

return NULL;
}


int main()
{
unsigned int i, ms;

homepath = getenv("HOME");

nthreads = get_ncpus_conf() + 2;
pthread_t threads[nthreads];

workers = malloc(sizeof(struct worker) * nthreads);
if (!workers) {
pr_err("Fail to malloc workers!");
exit(1);
}

for (i = 0; i < nthreads; i++) {
snprintf(workers[i].dir, sizeof(workers[i].dir), \
"%s/tmpmnt/thread%i", homepath, i + 1);

if( mkdir(workers[i].dir, 0700) ){
pr_err("Fail to mkdir workerdir");
exit(1);
}
}

printf("Running %d writer threads \n", nthreads);

run = 1;
for (i = 0; i < nthreads; i++) {
if( pthread_create(&threads[i], NULL, worker, &workers[i]) ) {
pr_err("Fail to create pthread");
exit(1);
}
}

for (ms = 0; ; ms++) {
usleep(1000);

if (ms >= 1000 && enospc_cnt)
break;

if (enospc_cnt > 100)
break;
}

run = 0;
for (i = 0; i < nthreads; i++){
if( pthread_join(threads[i], NULL) ) {
pr_err("Fail to pthread_join");
}
}

printf("Got %d ENOSPC runtime %dms \n", enospc_cnt, ms);

return 0;
}