From 86254969e56c97ec27180fbb75fdf5f179d959b3 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Fri, 7 Dec 2018 12:15:16 +0100 Subject: [PATCH] eo: make efl_future_then have a data pointer in addition of the object pointer. Summary: In the case when you have multiple future in flight related to one object, you couldn't use the previous version of efl_future_then. Now all function calls take a void* pointer that allow multiple future to have their private data request data accessible in all the callback. This should not break released API as Eo.h is not released yet and so was efl_future_Eina_FutureXXX_then. Depends on D7332 Reviewers: felipealmeida, segfaultxavi, vitor.sousa, SanghyeonLee, bu5hm4n Reviewed By: segfaultxavi Subscribers: #reviewers, #committers Tags: #efl Maniphest Tasks: T7472 Differential Revision: https://phab.enlightenment.org/D7379 --- src/lib/ecore/efl_io_copier.c | 14 +++++++------- src/lib/ecore_con/efl_net_dialer_http.c | 8 ++++---- src/lib/ecore_con/efl_net_dialer_ssl.c | 6 +++--- src/lib/ecore_con/efl_net_dialer_tcp.c | 6 +++--- src/lib/ecore_con/efl_net_dialer_udp.c | 6 +++--- src/lib/ecore_con/efl_net_dialer_unix.c | 6 +++--- src/lib/ecore_con/efl_net_dialer_websocket.c | 12 ++++++------ src/lib/ecore_con/efl_net_server_windows.c | 6 +++--- src/lib/eina/eina_promise_private.h | 18 +++++++++--------- src/lib/eo/Eo.h | 19 +++++++++++++------ src/lib/eo/eo_base_class.c | 6 +++--- src/tests/ecore/efl_app_test_promise.c | 14 +++++++------- 12 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/lib/ecore/efl_io_copier.c b/src/lib/ecore/efl_io_copier.c index 28f5ab6..a031688 100644 --- a/src/lib/ecore/efl_io_copier.c +++ b/src/lib/ecore/efl_io_copier.c @@ -72,7 +72,7 @@ static void _efl_io_copier_read(Eo *o, Efl_Io_Copier_Data *pd); while (0) static Eina_Value -_efl_io_copier_timeout_inactivity_cb(Eo *o, const Eina_Value v) +_efl_io_copier_timeout_inactivity_cb(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Eina_Error err = ETIMEDOUT; efl_event_callback_call(o, EFL_IO_COPIER_EVENT_ERROR, &err); @@ -86,12 +86,12 @@ _efl_io_copier_timeout_inactivity_reschedule(Eo *o, Efl_Io_Copier_Data *pd) if (pd->timeout_inactivity <= 0.0) return; efl_future_then(o, efl_loop_timeout(efl_loop_get(o), pd->timeout_inactivity), - .success = _efl_io_copier_timeout_inactivity_cb, - .storage = &pd->inactivity_timer); + .success = _efl_io_copier_timeout_inactivity_cb, + .storage = &pd->inactivity_timer); } static Eina_Value -_efl_io_copier_job(Eo *o, const Eina_Value v) +_efl_io_copier_job(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); uint64_t old_read = pd->progress.read; @@ -138,14 +138,14 @@ _efl_io_copier_job_schedule(Eo *o, Efl_Io_Copier_Data *pd) { Eina_Value v = EINA_VALUE_EMPTY; - v = _efl_io_copier_job(o, v); + v = _efl_io_copier_job(o, NULL, v); eina_value_flush(&v); } else { efl_future_then(o, efl_loop_job(efl_loop_get(o)), - .success = _efl_io_copier_job, - .storage = &pd->job); + .success = _efl_io_copier_job, + .storage = &pd->job); } } diff --git a/src/lib/ecore_con/efl_net_dialer_http.c b/src/lib/ecore_con/efl_net_dialer_http.c index bc004ba..8bfe967 100644 --- a/src/lib/ecore_con/efl_net_dialer_http.c +++ b/src/lib/ecore_con/efl_net_dialer_http.c @@ -1717,7 +1717,7 @@ _efl_net_dialer_http_efl_io_writer_can_write_set(Eo *o, Efl_Net_Dialer_Http_Data efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, NULL); } -static Eina_Value _efl_net_dialer_http_pending_close(Eo *o, Eina_Value value); +static Eina_Value _efl_net_dialer_http_pending_close(Eo *o, void *data, Eina_Value value); EOLIAN static Eina_Error _efl_net_dialer_http_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Http_Data *pd) @@ -1733,8 +1733,8 @@ _efl_net_dialer_http_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Http_Data *pd) if ((!pd->pending_close) && (pd->easy)) { efl_future_then(o, efl_loop_job(efl_loop_get(o)), - .success = _efl_net_dialer_http_pending_close, - .storage = &pd->pending_close); + .success = _efl_net_dialer_http_pending_close, + .storage = &pd->pending_close); DBG("dialer=%p closed from CURL callback, schedule close job=%p", o, pd->pending_close); } return 0; @@ -1766,7 +1766,7 @@ _efl_net_dialer_http_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Http_Data *pd) } static Eina_Value -_efl_net_dialer_http_pending_close(Eo *o, const Eina_Value value EINA_UNUSED) +_efl_net_dialer_http_pending_close(Eo *o, void *data EINA_UNUSED, const Eina_Value value EINA_UNUSED) { Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS); diff --git a/src/lib/ecore_con/efl_net_dialer_ssl.c b/src/lib/ecore_con/efl_net_dialer_ssl.c index d3e83f6..78b5879 100644 --- a/src/lib/ecore_con/efl_net_dialer_ssl.c +++ b/src/lib/ecore_con/efl_net_dialer_ssl.c @@ -128,7 +128,7 @@ _efl_net_dialer_ssl_ssl_context_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Ssl_ } static Eina_Value -_efl_net_dialer_ssl_connect_timeout(Eo *o, const Eina_Value v) +_efl_net_dialer_ssl_connect_timeout(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Eina_Error err = ETIMEDOUT; @@ -143,8 +143,8 @@ static void _timeout_schedule(Eo *o, Efl_Net_Dialer_Ssl_Data *pd, double timeout) { efl_future_then(o, efl_loop_timeout(efl_loop_get(o), timeout), - .success = _efl_net_dialer_ssl_connect_timeout, - .storage = &pd->connect_timeout); + .success = _efl_net_dialer_ssl_connect_timeout, + .storage = &pd->connect_timeout); } EOLIAN static Eina_Error diff --git a/src/lib/ecore_con/efl_net_dialer_tcp.c b/src/lib/ecore_con/efl_net_dialer_tcp.c index 57f5cf9..9a7a795 100644 --- a/src/lib/ecore_con/efl_net_dialer_tcp.c +++ b/src/lib/ecore_con/efl_net_dialer_tcp.c @@ -90,7 +90,7 @@ _efl_net_dialer_tcp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Tcp_Data *pd) } static Eina_Value -_efl_net_dialer_tcp_connect_timeout(Eo *o, const Eina_Value v) +_efl_net_dialer_tcp_connect_timeout(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Net_Dialer_Tcp_Data *pd = efl_data_scope_get(o, MY_CLASS); Eina_Error err = ETIMEDOUT; @@ -108,8 +108,8 @@ static void _timeout_schedule(Eo *o, Efl_Net_Dialer_Tcp_Data *pd) { efl_future_then(o, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial), - .success = _efl_net_dialer_tcp_connect_timeout, - .storage = &pd->connect.timeout); + .success = _efl_net_dialer_tcp_connect_timeout, + .storage = &pd->connect.timeout); } static void diff --git a/src/lib/ecore_con/efl_net_dialer_udp.c b/src/lib/ecore_con/efl_net_dialer_udp.c index f3e7ef2..351d077 100644 --- a/src/lib/ecore_con/efl_net_dialer_udp.c +++ b/src/lib/ecore_con/efl_net_dialer_udp.c @@ -82,7 +82,7 @@ _efl_net_dialer_udp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Udp_Data *pd) } static Eina_Value -_efl_net_dialer_udp_resolver_timeout(Eo *o, const Eina_Value v) +_efl_net_dialer_udp_resolver_timeout(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Net_Dialer_Udp_Data *pd = efl_data_scope_get(o, MY_CLASS); Eina_Error err = ETIMEDOUT; @@ -104,8 +104,8 @@ static void _timeout_schedule(Eo *o, Efl_Net_Dialer_Udp_Data *pd) { efl_future_then(o, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial), - .success = _efl_net_dialer_udp_resolver_timeout, - .storage = &pd->resolver.timeout); + .success = _efl_net_dialer_udp_resolver_timeout, + .storage = &pd->resolver.timeout); } static Eina_Error diff --git a/src/lib/ecore_con/efl_net_dialer_unix.c b/src/lib/ecore_con/efl_net_dialer_unix.c index 4426d44..641736b 100644 --- a/src/lib/ecore_con/efl_net_dialer_unix.c +++ b/src/lib/ecore_con/efl_net_dialer_unix.c @@ -71,7 +71,7 @@ _efl_net_dialer_unix_efl_object_destructor(Eo *o, Efl_Net_Dialer_Unix_Data *pd) } static Eina_Value -_efl_net_dialer_unix_connect_timeout(Eo *o, const Eina_Value v) +_efl_net_dialer_unix_connect_timeout(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Net_Dialer_Unix_Data *pd = efl_data_scope_get(o, MY_CLASS); Eina_Error err = ETIMEDOUT; @@ -130,8 +130,8 @@ static void _timeout_schedule(Eo *o, Efl_Net_Dialer_Unix_Data *pd) { efl_future_then(o, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial), - .success = _efl_net_dialer_unix_connect_timeout, - .storage = &pd->connect.timeout); + .success = _efl_net_dialer_unix_connect_timeout, + .storage = &pd->connect.timeout); } EOLIAN static Eina_Error diff --git a/src/lib/ecore_con/efl_net_dialer_websocket.c b/src/lib/ecore_con/efl_net_dialer_websocket.c index 3c5339d..30545f2 100644 --- a/src/lib/ecore_con/efl_net_dialer_websocket.c +++ b/src/lib/ecore_con/efl_net_dialer_websocket.c @@ -735,7 +735,7 @@ _efl_net_dialer_websocket_job_receive(Eo *o, Efl_Net_Dialer_Websocket_Data *pd) } static Eina_Value -_efl_net_dialer_websocket_job(Eo *o, const Eina_Value v) +_efl_net_dialer_websocket_job(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Net_Dialer_Websocket_Data *pd = efl_data_scope_get(o, MY_CLASS); @@ -765,8 +765,8 @@ _efl_net_dialer_websocket_job_schedule(Eo *o, Efl_Net_Dialer_Websocket_Data *pd) if (!loop) return; efl_future_then(o, efl_loop_job(loop), - .success = _efl_net_dialer_websocket_job, - .storage = &pd->job); + .success = _efl_net_dialer_websocket_job, + .storage = &pd->job); } static void @@ -1486,7 +1486,7 @@ _efl_net_dialer_websocket_binary_send(Eo *o, Efl_Net_Dialer_Websocket_Data *pd, } static Eina_Value -_efl_net_dialer_websocket_close_request_timeout(Eo *o, const Eina_Value v) +_efl_net_dialer_websocket_close_request_timeout(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { DBG("server did not close the TCP socket, timeout"); efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); @@ -1505,8 +1505,8 @@ _efl_net_dialer_websocket_close_request(Eo *o, Efl_Net_Dialer_Websocket_Data *pd eina_future_cancel(pd->close_timeout); efl_future_then(o, efl_loop_timeout(efl_loop_get(o), 2.0), - .success = _efl_net_dialer_websocket_close_request_timeout, - .storage = &pd->close_timeout); + .success = _efl_net_dialer_websocket_close_request_timeout, + .storage = &pd->close_timeout); efl_io_writer_can_write_set(o, EINA_FALSE); diff --git a/src/lib/ecore_con/efl_net_server_windows.c b/src/lib/ecore_con/efl_net_server_windows.c index 8859036..68e7fe9 100644 --- a/src/lib/ecore_con/efl_net_server_windows.c +++ b/src/lib/ecore_con/efl_net_server_windows.c @@ -312,7 +312,7 @@ _efl_net_server_windows_efl_net_server_address_get(const Eo *o EINA_UNUSED, Efl_ } static Eina_Value -_efl_net_server_windows_pending_announce_job(Eo *o, const Eina_Value v) +_efl_net_server_windows_pending_announce_job(Eo *o, void *data EINA_UNUSED, const Eina_Value v) { Efl_Net_Server_Windows_Data *pd = efl_data_scope_get(o, MY_CLASS); Eo *client; @@ -339,8 +339,8 @@ _efl_net_server_windows_pending_announce_job_schedule(Eo *o, Efl_Net_Server_Wind loop = efl_loop_get(o); if (!loop) return; efl_future_then(o, efl_loop_job(loop), - .success = _efl_net_server_windows_pending_announce_job, - .storage = &pd->pending_announcer_job); + .success = _efl_net_server_windows_pending_announce_job, + .storage = &pd->pending_announcer_job); } EOLIAN static void diff --git a/src/lib/eina/eina_promise_private.h b/src/lib/eina/eina_promise_private.h index 31375b5..1aeb2e7 100644 --- a/src/lib/eina/eina_promise_private.h +++ b/src/lib/eina/eina_promise_private.h @@ -1,38 +1,38 @@ #ifndef __EINA_PROMISE_PRIVATE_H__ #define __EINA_PROMISE_PRIVATE_H__ -#define ERROR_DISPATCH(_cbs, _ret, _value, _data) \ +#define ERROR_DISPATCH(_cbs, _ret, _value, ...) \ do { \ Eina_Error ERROR_DISPATCH__err; \ if (!(_cbs)->error) (_ret) = (_value); \ else \ { \ eina_value_get(&(_value), &ERROR_DISPATCH__err); \ - (_ret) = (_cbs)->error((_data), ERROR_DISPATCH__err); \ + (_ret) = (_cbs)->error(__VA_ARGS__, ERROR_DISPATCH__err); \ } \ } while (0) -#define EASY_FUTURE_DISPATCH(_ret, _value, _dead_future, _cbs, _data) \ +#define EASY_FUTURE_DISPATCH(_ret, _value, _dead_future, _cbs, ...) \ do { \ - if ((_value).type == EINA_VALUE_TYPE_ERROR) ERROR_DISPATCH((_cbs), (_ret), (_value), (_data)); \ + if ((_value).type == EINA_VALUE_TYPE_ERROR) ERROR_DISPATCH((_cbs), (_ret), (_value), __VA_ARGS__); \ else \ { \ if ((!(_cbs)->success_type) || ((_cbs)->success_type && (_value).type == (_cbs)->success_type)) \ { \ if (!(_cbs)->success) (_ret) = (_value); /* pass thru */ \ - else (_ret) = (_cbs)->success((_data), (_value)); \ + else (_ret) = (_cbs)->success(__VA_ARGS__, (_value)); \ } \ else \ { \ Eina_Value EASY_FUTURE_DISPATCH__err = EINA_VALUE_EMPTY; \ - ERR("Future %p, success cb: %p data: %p, expected success_type %p (%s), got %p (%s)", \ - _dead_future, (_cbs)->success, (_data), \ + ERR("Future %p, success cb: %p, expected success_type %p (%s), got %p (%s)", \ + _dead_future, (_cbs)->success, \ (_cbs)->success_type, eina_value_type_name_get((_cbs)->success_type), \ (_value).type, (_value).type ? eina_value_type_name_get((_value).type) : NULL); \ if (eina_value_setup(&EASY_FUTURE_DISPATCH__err, EINA_VALUE_TYPE_ERROR)) eina_value_set(&EASY_FUTURE_DISPATCH__err, EINVAL); \ - ERROR_DISPATCH((_cbs), (_ret), EASY_FUTURE_DISPATCH__err, (_data)); \ + ERROR_DISPATCH((_cbs), (_ret), EASY_FUTURE_DISPATCH__err, __VA_ARGS__); \ } \ } \ - if ((_cbs)->free) (_cbs)->free((_data), _dead_future); \ + if ((_cbs)->free) (_cbs)->free(__VA_ARGS__, _dead_future); \ } while(0) #endif diff --git a/src/lib/eo/Eo.h b/src/lib/eo/Eo.h index 8cd9f63..e953ac2 100644 --- a/src/lib/eo/Eo.h +++ b/src/lib/eo/Eo.h @@ -379,7 +379,7 @@ typedef struct _Efl_Future_Cb_Desc { * using @c eina_value_flush() once they are unused (no more future or futures * returned a new value). */ - Eina_Value (*success)(Eo *o, const Eina_Value value); + Eina_Value (*success)(Eo *o, void *data, const Eina_Value value); /** * Called on error (value.type is @c EINA_VALUE_TYPE_ERROR). * @@ -417,7 +417,7 @@ typedef struct _Efl_Future_Cb_Desc { * using @c eina_value_flush() once they are unused (no more future or futures * returned a new value). */ - Eina_Value (*error)(Eo *o, Eina_Error error); + Eina_Value (*error)(Eo *o, void *data, Eina_Error error); /** * Called on @b all situations to notify future destruction. * @@ -431,7 +431,7 @@ typedef struct _Efl_Future_Cb_Desc { * @param o The object used to create the link in efl_future_cb_from_desc() or efl_future_chain_array(). * @param dead_future The future that's been freed. */ - void (*free)(Eo *o, const Eina_Future *dead_future); + void (*free)(Eo *o, void *data, const Eina_Future *dead_future); /** * If provided, then @c success will only be called if the value type matches the given pointer. * @@ -440,6 +440,13 @@ typedef struct _Efl_Future_Cb_Desc { */ const Eina_Value_Type *success_type; /** + * Context data given to every callback. + * + * This must be freed @b only by @c free callback as it's called from every case, + * otherwise it may lead to memory leaks. + */ + const void *data; + /** * This is used by Eo to cancel pending futures in case * an Eo object is deleted. It can be @c NULL. */ @@ -476,7 +483,7 @@ typedef struct _Efl_Future_Cb_Desc { * } * * static Eina_Value - * _file_ok(Eo *o EINA_UNUSED, const Eina_Value value) + * _file_ok(Eo *o EINA_UNUSED, void *data EINA_UNUSED, const Eina_Value value) * { * const char *data; * //There's no need to check the value type since EO infra already has done so. @@ -487,7 +494,7 @@ typedef struct _Efl_Future_Cb_Desc { * } * * static Eina_Value - * _file_err(Eo *o EINA_UNUSED, Eina_Error error) + * _file_err(Eo *o EINA_UNUSED, void *data EINA_UNUSED, Eina_Error error) * { * //In case the downloader is deleted before the future is resolved, the future will be canceled thus this callback will be called. * fprintf(stderr, "Could not download the file. Reason: %s\n", eina_error_msg_get(error)); @@ -495,7 +502,7 @@ typedef struct _Efl_Future_Cb_Desc { * } * * static void - * _downlader_free(Eo *o, const Eina_Future *dead_future EINA_UNUSED) + * _downlader_free(Eo *o, void *data EINA_UNUSED, const Eina_Future *dead_future EINA_UNUSED) * { * Ecore_Timer *t = efl_key_data_get(o, "timer"); * //The download finished before the timer expired. Cancel it... diff --git a/src/lib/eo/eo_base_class.c b/src/lib/eo/eo_base_class.c index 928811a..3ca8591 100644 --- a/src/lib/eo/eo_base_class.c +++ b/src/lib/eo/eo_base_class.c @@ -2080,7 +2080,7 @@ _efl_future_cb(void *data, const Eina_Value value, const Eina_Future *dead_futur pd->pending_futures = eina_inlist_remove(pd->pending_futures, EINA_INLIST_GET(pending)); efl_ref(o); - EASY_FUTURE_DISPATCH(ret, value, dead_future, &pending->desc, (void*) o); + EASY_FUTURE_DISPATCH(ret, value, dead_future, &pending->desc, (void*) o, (void*) pending->desc.data); efl_unref(o); _efl_pending_future_free(pending); @@ -2147,10 +2147,10 @@ efl_future_chain_array(Eo *obj, { if (descs[i].error) { - Eina_Value r = descs[i].error(obj, ENOMEM); + Eina_Value r = descs[i].error(obj, (void*) descs[i].data, ENOMEM); eina_value_flush(&r); } - if (descs[i].free) descs[i].free(obj, NULL); + if (descs[i].free) descs[i].free(obj, (void*) descs[i].data, NULL); } return NULL; } diff --git a/src/tests/ecore/efl_app_test_promise.c b/src/tests/ecore/efl_app_test_promise.c index fde8879..e68f74d 100644 --- a/src/tests/ecore/efl_app_test_promise.c +++ b/src/tests/ecore/efl_app_test_promise.c @@ -808,7 +808,7 @@ EFL_START_TEST(efl_test_promise_future_race) EFL_END_TEST static Eina_Value -_eo_future1_ok(Eo *eo EINA_UNUSED, const Eina_Value v) +_eo_future1_ok(Eo *eo EINA_UNUSED, void *data EINA_UNUSED, const Eina_Value v) { const char *number; @@ -819,14 +819,14 @@ _eo_future1_ok(Eo *eo EINA_UNUSED, const Eina_Value v) } static Eina_Value -_eo_future1_err(Eo *eo EINA_UNUSED, Eina_Error err EINA_UNUSED) +_eo_future1_err(Eo *eo EINA_UNUSED, void *data EINA_UNUSED, Eina_Error err EINA_UNUSED) { //Should not happen fail_if(EINA_TRUE); } static Eina_Value -_eo_future2_ok(Eo *eo EINA_UNUSED, const Eina_Value v) +_eo_future2_ok(Eo *eo EINA_UNUSED, void *data EINA_UNUSED, const Eina_Value v) { //Should not happen fail_if(EINA_TRUE); @@ -834,7 +834,7 @@ _eo_future2_ok(Eo *eo EINA_UNUSED, const Eina_Value v) } static Eina_Value -_eo_future2_err(Eo *eo EINA_UNUSED, Eina_Error err) +_eo_future2_err(Eo *eo EINA_UNUSED, void *data EINA_UNUSED, Eina_Error err) { Eina_Value v; @@ -845,7 +845,7 @@ _eo_future2_err(Eo *eo EINA_UNUSED, Eina_Error err) } static void -_eo_future_free(Eo *eo, const Eina_Future *dead EINA_UNUSED) +_eo_future_free(Eo *eo, void *data EINA_UNUSED, const Eina_Future *dead EINA_UNUSED) { int *free_called = efl_key_data_get(eo, "free_called"); (*free_called)++; @@ -890,7 +890,7 @@ EFL_START_TEST(efl_test_promise_eo) EFL_END_TEST static Eina_Value -_eo_future_link_success(Eo *eo EINA_UNUSED, const Eina_Value v) +_eo_future_link_success(Eo *eo EINA_UNUSED, void *data EINA_UNUSED, const Eina_Value v) { //This should never happen fail_if(EINA_TRUE); @@ -898,7 +898,7 @@ _eo_future_link_success(Eo *eo EINA_UNUSED, const Eina_Value v) } static Eina_Value -_eo_future_link_err(Eo *eo, Eina_Error err) +_eo_future_link_err(Eo *eo, void *data EINA_UNUSED, Eina_Error err) { int *err_called = efl_key_data_get(eo, "err_called"); Eina_Value v; -- 2.7.4