give up USE_GC_MALLOC_OBJ_INFO_DETAILS
This feature is no longer possible under current design; now that our GC is pluggable, we cannot assume what was achieved by this compiler flag is always possble by the dynamically-loaded GC implementation.
This commit is contained in:
parent
9acc0efdc1
commit
fa6bf1da57
138
gc.c
138
gc.c
@ -467,7 +467,7 @@ int ruby_gc_debug_indent = 0;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef CALC_EXACT_MALLOC_SIZE
|
#ifndef CALC_EXACT_MALLOC_SIZE
|
||||||
# define CALC_EXACT_MALLOC_SIZE USE_GC_MALLOC_OBJ_INFO_DETAILS
|
# define CALC_EXACT_MALLOC_SIZE 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VALUE rb_mGC;
|
VALUE rb_mGC;
|
||||||
@ -4313,63 +4313,13 @@ rb_memerror(void)
|
|||||||
EC_JUMP_TAG(ec, TAG_RAISE);
|
EC_JUMP_TAG(ec, TAG_RAISE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CALC_EXACT_MALLOC_SIZE && USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
|
|
||||||
#define MALLOC_INFO_GEN_SIZE 100
|
|
||||||
#define MALLOC_INFO_SIZE_SIZE 10
|
|
||||||
static size_t malloc_info_gen_cnt[MALLOC_INFO_GEN_SIZE];
|
|
||||||
static size_t malloc_info_gen_size[MALLOC_INFO_GEN_SIZE];
|
|
||||||
static size_t malloc_info_size[MALLOC_INFO_SIZE_SIZE+1];
|
|
||||||
static st_table *malloc_info_file_table;
|
|
||||||
|
|
||||||
static int
|
|
||||||
mmalloc_info_file_i(st_data_t key, st_data_t val, st_data_t dmy)
|
|
||||||
{
|
|
||||||
const char *file = (void *)key;
|
|
||||||
const size_t *data = (void *)val;
|
|
||||||
|
|
||||||
fprintf(stderr, "%s\t%"PRIdSIZE"\t%"PRIdSIZE"\n", file, data[0], data[1]);
|
|
||||||
|
|
||||||
return ST_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((destructor))
|
|
||||||
void
|
|
||||||
rb_malloc_info_show_results(void)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
fprintf(stderr, "* malloc_info gen statistics\n");
|
|
||||||
for (i=0; i<MALLOC_INFO_GEN_SIZE; i++) {
|
|
||||||
if (i == MALLOC_INFO_GEN_SIZE-1) {
|
|
||||||
fprintf(stderr, "more\t%"PRIdSIZE"\t%"PRIdSIZE"\n", malloc_info_gen_cnt[i], malloc_info_gen_size[i]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "%d\t%"PRIdSIZE"\t%"PRIdSIZE"\n", i, malloc_info_gen_cnt[i], malloc_info_gen_size[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "* malloc_info size statistics\n");
|
|
||||||
for (i=0; i<MALLOC_INFO_SIZE_SIZE; i++) {
|
|
||||||
int s = 16 << i;
|
|
||||||
fprintf(stderr, "%d\t%"PRIdSIZE"\n", s, malloc_info_size[i]);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "more\t%"PRIdSIZE"\n", malloc_info_size[i]);
|
|
||||||
|
|
||||||
if (malloc_info_file_table) {
|
|
||||||
fprintf(stderr, "* malloc_info file statistics\n");
|
|
||||||
st_foreach(malloc_info_file_table, mmalloc_info_file_i, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
void
|
void
|
||||||
rb_malloc_info_show_results(void)
|
rb_malloc_info_show_results(void)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void *
|
void *
|
||||||
ruby_xmalloc_body(size_t size)
|
ruby_xmalloc(size_t size)
|
||||||
{
|
{
|
||||||
if ((ssize_t)size < 0) {
|
if ((ssize_t)size < 0) {
|
||||||
negative_size_allocation_error("too large allocation size");
|
negative_size_allocation_error("too large allocation size");
|
||||||
@ -4393,13 +4343,13 @@ xmalloc2_size(const size_t count, const size_t elsize)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
ruby_xmalloc2_body(size_t n, size_t size)
|
ruby_xmalloc2(size_t n, size_t size)
|
||||||
{
|
{
|
||||||
return rb_gc_impl_malloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
|
return rb_gc_impl_malloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
ruby_xcalloc_body(size_t n, size_t size)
|
ruby_xcalloc(size_t n, size_t size)
|
||||||
{
|
{
|
||||||
return rb_gc_impl_calloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
|
return rb_gc_impl_calloc(rb_gc_get_objspace(), xmalloc2_size(n, size));
|
||||||
}
|
}
|
||||||
@ -4418,7 +4368,7 @@ ruby_sized_xrealloc(void *ptr, size_t new_size, size_t old_size)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
ruby_xrealloc_body(void *ptr, size_t new_size)
|
ruby_xrealloc(void *ptr, size_t new_size)
|
||||||
{
|
{
|
||||||
return ruby_sized_xrealloc(ptr, new_size, 0);
|
return ruby_sized_xrealloc(ptr, new_size, 0);
|
||||||
}
|
}
|
||||||
@ -4434,7 +4384,7 @@ ruby_sized_xrealloc2(void *ptr, size_t n, size_t size, size_t old_n)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
ruby_xrealloc2_body(void *ptr, size_t n, size_t size)
|
ruby_xrealloc2(void *ptr, size_t n, size_t size)
|
||||||
{
|
{
|
||||||
return ruby_sized_xrealloc2(ptr, n, size, 0);
|
return ruby_sized_xrealloc2(ptr, n, size, 0);
|
||||||
}
|
}
|
||||||
@ -4519,11 +4469,6 @@ ruby_mimmalloc(size_t size)
|
|||||||
{
|
{
|
||||||
struct malloc_obj_info *info = mem;
|
struct malloc_obj_info *info = mem;
|
||||||
info->size = 0;
|
info->size = 0;
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
info->gen = 0;
|
|
||||||
info->file = NULL;
|
|
||||||
info->line = 0;
|
|
||||||
#endif
|
|
||||||
mem = info + 1;
|
mem = info + 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -4549,11 +4494,6 @@ ruby_mimcalloc(size_t num, size_t size)
|
|||||||
{
|
{
|
||||||
struct malloc_obj_info *info = mem;
|
struct malloc_obj_info *info = mem;
|
||||||
info->size = 0;
|
info->size = 0;
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
info->gen = 0;
|
|
||||||
info->file = NULL;
|
|
||||||
info->line = 0;
|
|
||||||
#endif
|
|
||||||
mem = info + 1;
|
mem = info + 1;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@ -4673,69 +4613,3 @@ Init_GC(void)
|
|||||||
|
|
||||||
rb_gc_impl_init();
|
rb_gc_impl_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ruby_xmalloc
|
|
||||||
#undef ruby_xmalloc
|
|
||||||
#endif
|
|
||||||
#ifdef ruby_xmalloc2
|
|
||||||
#undef ruby_xmalloc2
|
|
||||||
#endif
|
|
||||||
#ifdef ruby_xcalloc
|
|
||||||
#undef ruby_xcalloc
|
|
||||||
#endif
|
|
||||||
#ifdef ruby_xrealloc
|
|
||||||
#undef ruby_xrealloc
|
|
||||||
#endif
|
|
||||||
#ifdef ruby_xrealloc2
|
|
||||||
#undef ruby_xrealloc2
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void *
|
|
||||||
ruby_xmalloc(size_t size)
|
|
||||||
{
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
ruby_malloc_info_file = __FILE__;
|
|
||||||
ruby_malloc_info_line = __LINE__;
|
|
||||||
#endif
|
|
||||||
return ruby_xmalloc_body(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *
|
|
||||||
ruby_xmalloc2(size_t n, size_t size)
|
|
||||||
{
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
ruby_malloc_info_file = __FILE__;
|
|
||||||
ruby_malloc_info_line = __LINE__;
|
|
||||||
#endif
|
|
||||||
return ruby_xmalloc2_body(n, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *
|
|
||||||
ruby_xcalloc(size_t n, size_t size)
|
|
||||||
{
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
ruby_malloc_info_file = __FILE__;
|
|
||||||
ruby_malloc_info_line = __LINE__;
|
|
||||||
#endif
|
|
||||||
return ruby_xcalloc_body(n, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *
|
|
||||||
ruby_xrealloc(void *ptr, size_t new_size)
|
|
||||||
{
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
ruby_malloc_info_file = __FILE__;
|
|
||||||
ruby_malloc_info_line = __LINE__;
|
|
||||||
#endif
|
|
||||||
return ruby_xrealloc_body(ptr, new_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *
|
|
||||||
ruby_xrealloc2(void *ptr, size_t n, size_t new_size)
|
|
||||||
{
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
ruby_malloc_info_file = __FILE__;
|
|
||||||
ruby_malloc_info_line = __LINE__;
|
|
||||||
#endif
|
|
||||||
return ruby_xrealloc2_body(ptr, n, new_size);
|
|
||||||
}
|
|
||||||
|
69
gc/default.c
69
gc/default.c
@ -319,7 +319,7 @@ int ruby_rgengc_debug;
|
|||||||
# define GC_ENABLE_LAZY_SWEEP 1
|
# define GC_ENABLE_LAZY_SWEEP 1
|
||||||
#endif
|
#endif
|
||||||
#ifndef CALC_EXACT_MALLOC_SIZE
|
#ifndef CALC_EXACT_MALLOC_SIZE
|
||||||
# define CALC_EXACT_MALLOC_SIZE USE_GC_MALLOC_OBJ_INFO_DETAILS
|
# define CALC_EXACT_MALLOC_SIZE 0
|
||||||
#endif
|
#endif
|
||||||
#if defined(HAVE_MALLOC_USABLE_SIZE) || CALC_EXACT_MALLOC_SIZE > 0
|
#if defined(HAVE_MALLOC_USABLE_SIZE) || CALC_EXACT_MALLOC_SIZE > 0
|
||||||
# ifndef MALLOC_ALLOCATED_SIZE
|
# ifndef MALLOC_ALLOCATED_SIZE
|
||||||
@ -8360,18 +8360,8 @@ objspace_malloc_increase_body(rb_objspace_t *objspace, void *mem, size_t new_siz
|
|||||||
|
|
||||||
struct malloc_obj_info { /* 4 words */
|
struct malloc_obj_info { /* 4 words */
|
||||||
size_t size;
|
size_t size;
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
size_t gen;
|
|
||||||
const char *file;
|
|
||||||
size_t line;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
const char *ruby_malloc_info_file;
|
|
||||||
int ruby_malloc_info_line;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static inline size_t
|
static inline size_t
|
||||||
objspace_malloc_prepare(rb_objspace_t *objspace, size_t size)
|
objspace_malloc_prepare(rb_objspace_t *objspace, size_t size)
|
||||||
{
|
{
|
||||||
@ -8404,11 +8394,6 @@ objspace_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size)
|
|||||||
{
|
{
|
||||||
struct malloc_obj_info *info = mem;
|
struct malloc_obj_info *info = mem;
|
||||||
info->size = size;
|
info->size = size;
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
info->gen = objspace->profile.count;
|
|
||||||
info->file = ruby_malloc_info_file;
|
|
||||||
info->line = info->file ? ruby_malloc_info_line : 0;
|
|
||||||
#endif
|
|
||||||
mem = info + 1;
|
mem = info + 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -8478,58 +8463,6 @@ rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
|
|||||||
struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
|
struct malloc_obj_info *info = (struct malloc_obj_info *)ptr - 1;
|
||||||
ptr = info;
|
ptr = info;
|
||||||
old_size = info->size;
|
old_size = info->size;
|
||||||
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
{
|
|
||||||
int gen = (int)(objspace->profile.count - info->gen);
|
|
||||||
int gen_index = gen >= MALLOC_INFO_GEN_SIZE ? MALLOC_INFO_GEN_SIZE-1 : gen;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
malloc_info_gen_cnt[gen_index]++;
|
|
||||||
malloc_info_gen_size[gen_index] += info->size;
|
|
||||||
|
|
||||||
for (i=0; i<MALLOC_INFO_SIZE_SIZE; i++) {
|
|
||||||
size_t s = 16 << i;
|
|
||||||
if (info->size <= s) {
|
|
||||||
malloc_info_size[i]++;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
malloc_info_size[i]++;
|
|
||||||
found:;
|
|
||||||
|
|
||||||
{
|
|
||||||
st_data_t key = (st_data_t)info->file, d;
|
|
||||||
size_t *data;
|
|
||||||
|
|
||||||
if (malloc_info_file_table == NULL) {
|
|
||||||
malloc_info_file_table = st_init_numtable_with_size(1024);
|
|
||||||
}
|
|
||||||
if (st_lookup(malloc_info_file_table, key, &d)) {
|
|
||||||
/* hit */
|
|
||||||
data = (size_t *)d;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
data = malloc(xmalloc2_size(2, sizeof(size_t)));
|
|
||||||
if (data == NULL) rb_bug("objspace_xfree: can not allocate memory");
|
|
||||||
data[0] = data[1] = 0;
|
|
||||||
st_insert(malloc_info_file_table, key, (st_data_t)data);
|
|
||||||
}
|
|
||||||
data[0] ++;
|
|
||||||
data[1] += info->size;
|
|
||||||
};
|
|
||||||
if (0 && gen >= 2) { /* verbose output */
|
|
||||||
if (info->file) {
|
|
||||||
fprintf(stderr, "free - size:%"PRIdSIZE", gen:%d, pos: %s:%"PRIdSIZE"\n",
|
|
||||||
info->size, gen, info->file, info->line);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "free - size:%"PRIdSIZE", gen:%d\n",
|
|
||||||
info->size, gen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
old_size = objspace_malloc_size(objspace, ptr, old_size);
|
old_size = objspace_malloc_size(objspace, ptr, old_size);
|
||||||
|
|
||||||
|
@ -283,110 +283,6 @@ void ruby_xfree(void *ptr)
|
|||||||
RBIMPL_ATTR_NOEXCEPT(free(ptr))
|
RBIMPL_ATTR_NOEXCEPT(free(ptr))
|
||||||
;
|
;
|
||||||
|
|
||||||
#if USE_GC_MALLOC_OBJ_INFO_DETAILS
|
|
||||||
# define ruby_xmalloc(s1) ruby_xmalloc_with_location(s1, __FILE__, __LINE__)
|
|
||||||
# define ruby_xmalloc2(s1, s2) ruby_xmalloc2_with_location(s1, s2, __FILE__, __LINE__)
|
|
||||||
# define ruby_xcalloc(s1, s2) ruby_xcalloc_with_location(s1, s2, __FILE__, __LINE__)
|
|
||||||
# define ruby_xrealloc(ptr, s1) ruby_xrealloc_with_location(ptr, s1, __FILE__, __LINE__)
|
|
||||||
# define ruby_xrealloc2(ptr, s1, s2) ruby_xrealloc2_with_location(ptr, s1, s2, __FILE__, __LINE__)
|
|
||||||
|
|
||||||
RBIMPL_ATTR_NODISCARD()
|
|
||||||
RBIMPL_ATTR_RESTRICT()
|
|
||||||
RBIMPL_ATTR_RETURNS_NONNULL()
|
|
||||||
RBIMPL_ATTR_ALLOC_SIZE((1))
|
|
||||||
void *ruby_xmalloc_body(size_t size)
|
|
||||||
RBIMPL_ATTR_NOEXCEPT(malloc(size))
|
|
||||||
;
|
|
||||||
|
|
||||||
RBIMPL_ATTR_NODISCARD()
|
|
||||||
RBIMPL_ATTR_RESTRICT()
|
|
||||||
RBIMPL_ATTR_RETURNS_NONNULL()
|
|
||||||
RBIMPL_ATTR_ALLOC_SIZE((1,2))
|
|
||||||
void *ruby_xmalloc2_body(size_t nelems, size_t elemsiz)
|
|
||||||
RBIMPL_ATTR_NOEXCEPT(malloc(nelems * elemsiz))
|
|
||||||
;
|
|
||||||
|
|
||||||
RBIMPL_ATTR_NODISCARD()
|
|
||||||
RBIMPL_ATTR_RESTRICT()
|
|
||||||
RBIMPL_ATTR_RETURNS_NONNULL()
|
|
||||||
RBIMPL_ATTR_ALLOC_SIZE((1,2))
|
|
||||||
void *ruby_xcalloc_body(size_t nelems, size_t elemsiz)
|
|
||||||
RBIMPL_ATTR_NOEXCEPT(calloc(nelems, elemsiz))
|
|
||||||
;
|
|
||||||
|
|
||||||
RBIMPL_ATTR_NODISCARD()
|
|
||||||
RBIMPL_ATTR_RETURNS_NONNULL()
|
|
||||||
RBIMPL_ATTR_ALLOC_SIZE((2))
|
|
||||||
void *ruby_xrealloc_body(void *ptr, size_t newsiz)
|
|
||||||
RBIMPL_ATTR_NOEXCEPT(realloc(ptr, newsiz))
|
|
||||||
;
|
|
||||||
|
|
||||||
RBIMPL_ATTR_NODISCARD()
|
|
||||||
RBIMPL_ATTR_RETURNS_NONNULL()
|
|
||||||
RBIMPL_ATTR_ALLOC_SIZE((2,3))
|
|
||||||
void *ruby_xrealloc2_body(void *ptr, size_t newelems, size_t newsiz)
|
|
||||||
RBIMPL_ATTR_NOEXCEPT(realloc(ptr, newelems * newsiz))
|
|
||||||
;
|
|
||||||
|
|
||||||
RUBY_EXTERN const char *ruby_malloc_info_file;
|
|
||||||
RUBY_EXTERN int ruby_malloc_info_line;
|
|
||||||
|
|
||||||
static inline void *
|
|
||||||
ruby_xmalloc_with_location(size_t s, const char *file, int line)
|
|
||||||
{
|
|
||||||
void *ptr;
|
|
||||||
ruby_malloc_info_file = file;
|
|
||||||
ruby_malloc_info_line = line;
|
|
||||||
ptr = ruby_xmalloc_body(s);
|
|
||||||
ruby_malloc_info_file = NULL;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *
|
|
||||||
ruby_xmalloc2_with_location(size_t s1, size_t s2, const char *file, int line)
|
|
||||||
{
|
|
||||||
void *ptr;
|
|
||||||
ruby_malloc_info_file = file;
|
|
||||||
ruby_malloc_info_line = line;
|
|
||||||
ptr = ruby_xmalloc2_body(s1, s2);
|
|
||||||
ruby_malloc_info_file = NULL;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *
|
|
||||||
ruby_xcalloc_with_location(size_t s1, size_t s2, const char *file, int line)
|
|
||||||
{
|
|
||||||
void *ptr;
|
|
||||||
ruby_malloc_info_file = file;
|
|
||||||
ruby_malloc_info_line = line;
|
|
||||||
ptr = ruby_xcalloc_body(s1, s2);
|
|
||||||
ruby_malloc_info_file = NULL;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *
|
|
||||||
ruby_xrealloc_with_location(void *ptr, size_t s, const char *file, int line)
|
|
||||||
{
|
|
||||||
void *rptr;
|
|
||||||
ruby_malloc_info_file = file;
|
|
||||||
ruby_malloc_info_line = line;
|
|
||||||
rptr = ruby_xrealloc_body(ptr, s);
|
|
||||||
ruby_malloc_info_file = NULL;
|
|
||||||
return rptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *
|
|
||||||
ruby_xrealloc2_with_location(void *ptr, size_t s1, size_t s2, const char *file, int line)
|
|
||||||
{
|
|
||||||
void *rptr;
|
|
||||||
ruby_malloc_info_file = file;
|
|
||||||
ruby_malloc_info_line = line;
|
|
||||||
rptr = ruby_xrealloc2_body(ptr, s1, s2);
|
|
||||||
ruby_malloc_info_file = NULL;
|
|
||||||
return rptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RBIMPL_SYMBOL_EXPORT_END()
|
RBIMPL_SYMBOL_EXPORT_END()
|
||||||
|
|
||||||
#endif /* RBIMPL_XMALLOC_H */
|
#endif /* RBIMPL_XMALLOC_H */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user