if (test)
{
ef->delete_me_now = 1;
- eet_internal_close(ef, 0);
+ eet_internal_close(ef, 1);
}
return test;
}
}
/* add to end of cache */
+/* this should only be called when the cache lock is already held */
static void
eet_cache_add(Eet_File *ef, Eet_File ***cache, int *cache_num, int *cache_alloc)
{
}
/* delete from cache */
+/* this should only be called when the cache lock is already held */
static void
eet_cache_del(Eet_File *ef, Eet_File ***cache, int *cache_num, int *cache_alloc)
{
* We need to compute the list of eet file to close separately from the cache,
* due to eet_close removing them from the cache after each call.
*/
+ LOCK_CACHE;
for (i = 0; i < eet_writers_num; i++)
{
if (eet_writers[i]->references <= 0) num++;
for (i = 0; i < num; i++)
{
- eet_close(closelist[i]);
+ eet_internal_close(closelist[i], 1);
}
}
+ UNLOCK_CACHE;
}
/* FIXME: MMAP race condition in READ_WRITE_MODE */
}
#endif
+/*
+ * this should only be called when the cache lock is already held
+ * (We could drop this restriction if we add a parameter to eet_test_close
+ * that indicates if the lock is held or not. For now it is easiest
+ * to just require that it is always held.)
+ */
static Eet_File *
eet_internal_read(Eet_File *ef)
{
/* check to see its' an eet file pointer */
if (eet_check_pointer(ef))
return EET_ERROR_BAD_OBJECT;
+
+ if (!locked) LOCK_CACHE;
+
/* deref */
ef->references--;
/* if its still referenced - dont go any further */
/* if not urgent to delete it - dont free it - leave it in cache */
if ((!ef->delete_me_now) && (ef->mode == EET_FILE_MODE_READ))
- return EET_ERROR_NONE;
-
- /* remove from cache */
- if (!locked)
{
- LOCK_CACHE;
+ if (!locked) UNLOCK_CACHE;
+ return EET_ERROR_NONE;
}
+
+ /* remove from cache */
if (ef->mode == EET_FILE_MODE_READ)
eet_cache_del(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);
else if ((ef->mode == EET_FILE_MODE_WRITE) || (ef->mode == EET_FILE_MODE_READ_WRITE))
eet_cache_del(ef, &eet_writers, &eet_writers_num, &eet_writers_alloc);
+
+ /* we can unlock the cache now */
if (!locked)
{
UNLOCK_CACHE;
ef->sha1 = NULL;
ef->sha1_length = 0;
- return eet_internal_read(ef);
+ /* eet_internal_read expects the cache lock to be held when it is called */
+ LOCK_CACHE;
+ ef = eet_internal_read(ef);
+ UNLOCK_CACHE;
+ return ef;
}
EAPI Eet_File *
}
ef = eet_cache_find((char *)file, eet_writers, eet_writers_num);
}
- UNLOCK_CACHE;
/* try open the file based on mode */
if ((mode == EET_FILE_MODE_READ) || (mode == EET_FILE_MODE_READ_WRITE))
file_stat.st_mtime = 0;
fp = fopen(file, "rb");
- if (!fp) goto on_error;
+ if (!fp) goto open_error;
if (fstat(fileno(fp), &file_stat))
{
fclose(fp);
fp = NULL;
- goto on_error;
+ goto open_error;
}
if ((mode == EET_FILE_MODE_READ) &&
(file_stat.st_size < ((int) sizeof(int) * 3)))
{
fclose(fp);
fp = NULL;
- goto on_error;
+ goto open_error;
}
- on_error:
- if (fp == NULL && mode == EET_FILE_MODE_READ) return NULL;
+ open_error:
+ if (fp == NULL && mode == EET_FILE_MODE_READ) goto on_error;
}
else
{
unlink(file);
fd = open(file, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
fp = fdopen(fd, "wb");
- if (!fp) return NULL;
+ if (!fp) goto on_error;
}
/* We found one */
/* reference it up and return it */
if (fp != NULL) fclose(fp);
ef->references++;
+ UNLOCK_CACHE;
return ef;
}
/* Allocate struct for eet file and have it zero'd out */
ef = malloc(sizeof(Eet_File) + file_len);
if (!ef)
- return NULL;
+ goto on_error;
/* fill some of the members */
INIT_FILE(ef);
/* if we can't open - bail out */
if (eet_test_close(!ef->fp, ef))
- return NULL;
+ goto on_error;
fcntl(fileno(ef->fp), F_SETFD, FD_CLOEXEC);
/* if we opened for read or read-write */
ef->data = mmap(NULL, ef->data_size, PROT_READ,
MAP_SHARED, fileno(ef->fp), 0);
if (eet_test_close((ef->data == MAP_FAILED), ef))
- return NULL;
+ goto on_error;
ef = eet_internal_read(ef);
if (!ef)
- return NULL;
+ goto on_error;
}
empty_file:
/* add to cache */
if (ef->references == 1)
{
- LOCK_CACHE;
if (ef->mode == EET_FILE_MODE_READ)
eet_cache_add(ef, &eet_readers, &eet_readers_num, &eet_readers_alloc);
else
if ((ef->mode == EET_FILE_MODE_WRITE) || (ef->mode == EET_FILE_MODE_READ_WRITE))
eet_cache_add(ef, &eet_writers, &eet_writers_num, &eet_writers_alloc);
- UNLOCK_CACHE;
}
+ UNLOCK_CACHE;
return ef;
+
+on_error:
+ UNLOCK_CACHE;
+ return NULL;
}
EAPI Eet_File_Mode
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
+#include <pthread.h>
#include <check.h>
}
END_TEST
+static Eina_Bool open_worker_stop;
+static void*
+open_close_worker(void* path)
+{
+ while (!open_worker_stop)
+ {
+ Eet_File* ef = eet_open((char const*)path, EET_FILE_MODE_READ);
+ if (ef == NULL)
+ {
+ pthread_exit("eet_open() failed");
+ }
+ else
+ {
+ Eet_Error err_code = eet_close(ef);
+ if (err_code != EET_ERROR_NONE)
+ pthread_exit("eet_close() failed");
+ }
+ }
+
+ pthread_exit(NULL);
+}
+
+START_TEST(eet_cache_concurrency)
+{
+ char *file = strdup("/tmp/eet_suite_testXXXXXX");
+ const char *buffer = "test data";
+ Eet_File *ef;
+ void *thread_ret;
+ unsigned int n;
+
+ eet_init();
+
+ /* create a file to test with */
+ fail_if(!mktemp(file));
+ ef = eet_open(file, EET_FILE_MODE_WRITE);
+ fail_if(!ef);
+ fail_if(!eet_write(ef, "keys/tests", buffer, strlen(buffer) + 1, 0));
+
+ /* start a thread that repeatedly opens and closes a file */
+ open_worker_stop = 0;
+ pthread_t thread;
+ pthread_create(&thread, NULL, open_close_worker, file);
+
+ /* clear the cache repeatedly in this thread */
+ for (n = 0; n < 100000; ++n)
+ {
+ eet_clearcache();
+ }
+
+ /* join the other thread, and fail if it returned an error message */
+ open_worker_stop = 1;
+ fail_if(pthread_join(thread, &thread_ret) != 0);
+ fail_unless(thread_ret == NULL, (char const*)thread_ret);
+
+ fail_if(unlink(file) != 0);
+ eet_shutdown();
+}
+END_TEST
+
Suite *
eet_suite(void)
suite_add_tcase(s, tc);
#endif
+ tc = tcase_create("Eet Cache");
+ tcase_add_test(tc, eet_cache_concurrency);
+ suite_add_tcase(s, tc);
+
return s;
}