clean up closer logic, improve error handling, simplify logic, add comments

This commit is contained in:
Christian Grothoff 2020-03-15 20:42:47 +01:00
parent 4322bbf2f1
commit c898a1e13b
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
3 changed files with 110 additions and 76 deletions

View File

@ -63,10 +63,25 @@ static struct GNUNET_SCHEDULER_Task *task;
static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval; static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
/** /**
* Value to return from main(). #GNUNET_OK on success, #GNUNET_SYSERR * Value to return from main(). 0 on success, non-zero
* on serious errors. * on serious errors.
*/ */
static int global_ret; static enum
{
GR_SUCCESS = 0,
GR_WIRE_ACCOUNT_NOT_CONFIGURED = 1,
GR_WIRE_TRANSFER_FEES_NOT_CONFIGURED = 2,
GR_FAILURE_TO_ROUND_AMOUNT = 3,
GR_DATABASE_INSERT_HARD_FAIL = 4,
GR_DATABASE_SELECT_HARD_FAIL = 5,
GR_DATABASE_COMMIT_HARD_FAIL = 6,
GR_DATABASE_SESSION_START_FAIL = 7,
GR_DATABASE_TRANSACTION_BEGIN_FAIL = 8,
GR_CONFIGURATION_INVALID = 9,
GR_CMD_LINE_UTF8_ERROR = 10,
GR_CMD_LINE_OPTIONS_WRONG = 11,
GR_INVALID_PAYTO_ENCOUNTERED = 12,
} global_ret;
/** /**
* #GNUNET_YES if we are in test mode and should exit when idle. * #GNUNET_YES if we are in test mode and should exit when idle.
@ -113,7 +128,7 @@ shutdown_task (void *cls)
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
*/ */
static int static int
parse_wirewatch_config () parse_wirewatch_config (void)
{ {
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, GNUNET_CONFIGURATION_get_value_string (cfg,
@ -204,11 +219,6 @@ struct ExpiredReserveContext
*/ */
struct TALER_EXCHANGEDB_Session *session; struct TALER_EXCHANGEDB_Session *session;
/**
* Set to #GNUNET_YES if the transaction continues
* asynchronously.
*/
int async_cont;
}; };
@ -258,7 +268,7 @@ expired_reserve_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire account configured to deal with target URI `%s'\n", "No wire account configured to deal with target URI `%s'\n",
account_payto_uri); account_payto_uri);
global_ret = GNUNET_SYSERR; global_ret = GR_WIRE_ACCOUNT_NOT_CONFIGURED;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
@ -275,7 +285,7 @@ expired_reserve_cb (void *cls,
session); session);
if (NULL == af) if (NULL == af)
{ {
global_ret = GNUNET_SYSERR; global_ret = GR_WIRE_TRANSFER_FEES_NOT_CONFIGURED;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
@ -302,7 +312,7 @@ expired_reserve_cb (void *cls,
&currency_round_unit)) &currency_round_unit))
{ {
GNUNET_break (0); GNUNET_break (0);
global_ret = GNUNET_SYSERR; global_ret = GR_FAILURE_TO_ROUND_AMOUNT;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
@ -341,7 +351,7 @@ expired_reserve_cb (void *cls,
(GNUNET_DB_STATUS_HARD_ERROR == qs) ) (GNUNET_DB_STATUS_HARD_ERROR == qs) )
{ {
GNUNET_break (0); GNUNET_break (0);
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_INSERT_HARD_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
@ -352,20 +362,14 @@ expired_reserve_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Reserve was virtually empty, moving on\n"); "Reserve was virtually empty, moving on\n");
(void) commit_or_warn (session); (void) commit_or_warn (session);
erc->async_cont = GNUNET_YES;
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
NULL);
return qs; return qs;
} }
/* success, perform wire transfer */ /* success, perform wire transfer */
{ {
char *method;
void *buf; void *buf;
size_t buf_size; size_t buf_size;
method = TALER_payto_get_method (account_payto_uri);
TALER_BANK_prepare_transfer (account_payto_uri, TALER_BANK_prepare_transfer (account_payto_uri,
&amount_without_fee, &amount_without_fee,
exchange_base_url, exchange_base_url,
@ -375,15 +379,16 @@ expired_reserve_cb (void *cls,
/* Commit our intention to execute the wire transfer! */ /* Commit our intention to execute the wire transfer! */
qs = db_plugin->wire_prepare_data_insert (db_plugin->cls, qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
session, session,
method, wa->method,
buf, buf,
buf_size); buf_size);
GNUNET_free (buf); GNUNET_free (buf);
GNUNET_free (method);
} }
if (GNUNET_DB_STATUS_HARD_ERROR == qs) if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{ {
GNUNET_break (0); GNUNET_break (0);
global_ret = GR_DATABASE_INSERT_HARD_FAIL;
GNUNET_SCHEDULER_shutdown ();
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
if (GNUNET_DB_STATUS_SOFT_ERROR == qs) if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -391,10 +396,6 @@ expired_reserve_cb (void *cls,
/* start again */ /* start again */
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
} }
erc->async_cont = GNUNET_YES;
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
NULL);
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
} }
@ -419,7 +420,7 @@ run_reserve_closures (void *cls)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain database session!\n"); "Failed to obtain database session!\n");
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_SESSION_START_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
} }
@ -431,12 +432,11 @@ run_reserve_closures (void *cls)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n"); "Failed to start database transaction!\n");
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
} }
erc.session = session; erc.session = session;
erc.async_cont = GNUNET_NO;
now = GNUNET_TIME_absolute_get (); now = GNUNET_TIME_absolute_get ();
(void) GNUNET_TIME_round_abs (&now); (void) GNUNET_TIME_round_abs (&now);
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -454,7 +454,7 @@ run_reserve_closures (void *cls)
GNUNET_break (0); GNUNET_break (0);
db_plugin->rollback (db_plugin->cls, db_plugin->rollback (db_plugin->cls,
session); session);
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_SELECT_HARD_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
case GNUNET_DB_STATUS_SOFT_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR:
@ -483,8 +483,6 @@ run_reserve_closures (void *cls)
return; return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
(void) commit_or_warn (session); (void) commit_or_warn (session);
if (GNUNET_YES == erc.async_cont)
break;
GNUNET_assert (NULL == task); GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_reserve_closures, task = GNUNET_SCHEDULER_add_now (&run_reserve_closures,
NULL); NULL);
@ -494,7 +492,9 @@ run_reserve_closures (void *cls)
/** /**
* First task. * First task. Parses the configuration and starts the
* main loop of #run_reserve_closures(). Also schedules
* the #shutdown_task() to clean up.
* *
* @param cls closure, NULL * @param cls closure, NULL
* @param args remaining command-line arguments * @param args remaining command-line arguments
@ -515,7 +515,7 @@ run (void *cls,
if (GNUNET_OK != parse_wirewatch_config ()) if (GNUNET_OK != parse_wirewatch_config ())
{ {
cfg = NULL; cfg = NULL;
global_ret = 1; global_ret = GR_CONFIGURATION_INVALID;
return; return;
} }
GNUNET_assert (NULL == task); GNUNET_assert (NULL == task);
@ -531,7 +531,7 @@ run (void *cls,
* *
* @param argc number of arguments from the command line * @param argc number of arguments from the command line
* @param argv command line arguments * @param argv command line arguments
* @return 0 ok, 1 on error * @return 0 ok, non-zero on error
*/ */
int int
main (int argc, main (int argc,
@ -551,7 +551,7 @@ main (int argc,
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv, GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv)) &argc, &argv))
return 2; return GR_CMD_LINE_UTF8_ERROR;
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_PROGRAM_run (argc, argv, GNUNET_PROGRAM_run (argc, argv,
"taler-exchange-closer", "taler-exchange-closer",
@ -561,7 +561,7 @@ main (int argc,
&run, NULL)) &run, NULL))
{ {
GNUNET_free ((void *) argv); GNUNET_free ((void *) argv);
return 1; return GR_CMD_LINE_OPTIONS_WRONG;
} }
GNUNET_free ((void *) argv); GNUNET_free ((void *) argv);
return global_ret; return global_ret;

View File

@ -13,7 +13,6 @@
You should have received a copy of the GNU Affero General Public License along with You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* @file taler-exchange-transfer.c * @file taler-exchange-transfer.c
* @brief Process that actually finalizes outgoing transfers with the wire gateway / bank * @brief Process that actually finalizes outgoing transfers with the wire gateway / bank
@ -98,10 +97,23 @@ static struct GNUNET_CURL_RescheduleContext *rc;
static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval; static struct GNUNET_TIME_Relative aggregator_idle_sleep_interval;
/** /**
* Value to return from main(). #GNUNET_OK on success, #GNUNET_SYSERR * Value to return from main(). 0 on success, non-zero on errors.
* on serious errors.
*/ */
static int global_ret; static enum
{
GR_SUCCESS = 0,
GR_WIRE_TRANSFER_FAILED = 1,
GR_DATABASE_COMMIT_HARD_FAIL = 2,
GR_INVARIANT_FAILURE = 3,
GR_WIRE_ACCOUNT_NOT_CONFIGURED = 4,
GR_WIRE_TRANSFER_BEGIN_FAIL = 5,
GR_DATABASE_TRANSACTION_BEGIN_FAIL = 6,
GR_DATABASE_SESSION_START_FAIL = 7,
GR_CONFIGURATION_INVALID = 8,
GR_CMD_LINE_UTF8_ERROR = 9,
GR_CMD_LINE_OPTIONS_WRONG = 10,
GR_DATABASE_FETCH_FAILURE = 11,
} global_ret;
/** /**
* #GNUNET_YES if we are in test mode and should exit when idle. * #GNUNET_YES if we are in test mode and should exit when idle.
@ -109,16 +121,6 @@ static int global_ret;
static int test_mode; static int test_mode;
/**
* Execute the wire transfers that we have committed to
* do.
*
* @param cls NULL
*/
static void
run_transfers (void *cls);
/** /**
* We're being aborted with CTRL-C (or SIGTERM). Shut down. * We're being aborted with CTRL-C (or SIGTERM). Shut down.
* *
@ -170,7 +172,7 @@ shutdown_task (void *cls)
* @return #GNUNET_OK on success * @return #GNUNET_OK on success
*/ */
static int static int
parse_wirewatch_config () parse_wirewatch_config (void)
{ {
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg, GNUNET_CONFIGURATION_get_value_time (cfg,
@ -226,8 +228,21 @@ commit_or_warn (struct TALER_EXCHANGEDB_Session *session)
} }
/**
* Execute the wire transfers that we have committed to
* do.
*
* @param cls NULL
*/
static void
run_transfers (void *cls);
/** /**
* Function called with the result from the execute step. * Function called with the result from the execute step.
* On success, we mark the respective wire transfer as finished,
* and in general we afterwards continue to #run_transfers(),
* except for irrecoverable errors.
* *
* @param cls NULL * @param cls NULL
* @param http_status_code #MHD_HTTP_OK on success * @param http_status_code #MHD_HTTP_OK on success
@ -257,7 +272,7 @@ wire_confirm_cb (void *cls,
ec); ec);
db_plugin->rollback (db_plugin->cls, db_plugin->rollback (db_plugin->cls,
session); session);
global_ret = GNUNET_SYSERR; global_ret = GR_WIRE_TRANSFER_FAILED;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
GNUNET_free (wpd); GNUNET_free (wpd);
wpd = NULL; wpd = NULL;
@ -280,7 +295,7 @@ wire_confirm_cb (void *cls,
} }
else else
{ {
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_COMMIT_HARD_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
} }
GNUNET_free (wpd); GNUNET_free (wpd);
@ -299,7 +314,7 @@ wire_confirm_cb (void *cls,
return; return;
case GNUNET_DB_STATUS_HARD_ERROR: case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0); GNUNET_break (0);
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_COMMIT_HARD_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
@ -313,7 +328,7 @@ wire_confirm_cb (void *cls,
return; return;
default: default:
GNUNET_break (0); GNUNET_break (0);
global_ret = GNUNET_SYSERR; global_ret = GR_INVARIANT_FAILURE;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
} }
@ -321,7 +336,8 @@ wire_confirm_cb (void *cls,
/** /**
* Callback with data about a prepared transaction. * Callback with data about a prepared transaction. Triggers the respective
* wire transfer using the prepared transaction data.
* *
* @param cls NULL * @param cls NULL
* @param rowid row identifier used to mark prepared transaction as done * @param rowid row identifier used to mark prepared transaction as done
@ -339,6 +355,15 @@ wire_prepare_cb (void *cls,
struct TALER_EXCHANGEDB_WireAccount *wa; struct TALER_EXCHANGEDB_WireAccount *wa;
(void) cls; (void) cls;
if ( (NULL == wire_method) ||
(NULL == buf) )
{
GNUNET_break (0);
db_plugin->rollback (db_plugin->cls,
wpd->session);
global_ret = GR_DATABASE_FETCH_FAILURE;
goto cleanup;
}
wpd->row_id = rowid; wpd->row_id = rowid;
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting wire transfer %llu\n", "Starting wire transfer %llu\n",
@ -351,11 +376,8 @@ wire_prepare_cb (void *cls,
GNUNET_break (0); GNUNET_break (0);
db_plugin->rollback (db_plugin->cls, db_plugin->rollback (db_plugin->cls,
wpd->session); wpd->session);
global_ret = GNUNET_SYSERR; global_ret = GR_WIRE_ACCOUNT_NOT_CONFIGURED;
GNUNET_SCHEDULER_shutdown (); goto cleanup;
GNUNET_free (wpd);
wpd = NULL;
return;
} }
wa = wpd->wa; wa = wpd->wa;
wpd->eh = TALER_BANK_transfer (ctx, wpd->eh = TALER_BANK_transfer (ctx,
@ -369,12 +391,14 @@ wire_prepare_cb (void *cls,
GNUNET_break (0); /* Irrecoverable */ GNUNET_break (0); /* Irrecoverable */
db_plugin->rollback (db_plugin->cls, db_plugin->rollback (db_plugin->cls,
wpd->session); wpd->session);
global_ret = GNUNET_SYSERR; global_ret = GR_WIRE_TRANSFER_BEGIN_FAIL;
goto cleanup;
}
return;
cleanup:
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
GNUNET_free (wpd); GNUNET_free (wpd);
wpd = NULL; wpd = NULL;
return;
}
} }
@ -398,7 +422,7 @@ run_transfers (void *cls)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain database session!\n"); "Failed to obtain database session!\n");
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_SESSION_START_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
} }
@ -409,7 +433,7 @@ run_transfers (void *cls)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n"); "Failed to start database transaction!\n");
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_TRANSACTION_BEGIN_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
} }
@ -429,7 +453,7 @@ run_transfers (void *cls)
{ {
case GNUNET_DB_STATUS_HARD_ERROR: case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0); GNUNET_break (0);
global_ret = GNUNET_SYSERR; global_ret = GR_DATABASE_COMMIT_HARD_FAIL;
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; return;
case GNUNET_DB_STATUS_SOFT_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR:
@ -440,15 +464,17 @@ run_transfers (void *cls)
return; return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* no more prepared wire transfers, go sleep a bit! */ /* no more prepared wire transfers, go sleep a bit! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No more pending wire transfers, going idle\n");
GNUNET_assert (NULL == task); GNUNET_assert (NULL == task);
if (GNUNET_YES == test_mode) if (GNUNET_YES == test_mode)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No more pending wire transfers, shutting down (because we are in test mode)\n");
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
} }
else else
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No more pending wire transfers, going idle\n");
task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval, task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
&run_transfers, &run_transfers,
NULL); NULL);
@ -483,7 +509,7 @@ run (void *cls,
if (GNUNET_OK != parse_wirewatch_config ()) if (GNUNET_OK != parse_wirewatch_config ())
{ {
cfg = NULL; cfg = NULL;
global_ret = 1; global_ret = GR_CONFIGURATION_INVALID;
return; return;
} }
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
@ -525,19 +551,20 @@ main (int argc,
GNUNET_GETOPT_OPTION_END GNUNET_GETOPT_OPTION_END
}; };
if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv)) &argc, &argv))
return 2; return GR_CMD_LINE_UTF8_ERROR;
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_PROGRAM_run (argc, argv, GNUNET_PROGRAM_run (argc, argv,
"taler-exchange-transfers", "taler-exchange-transfer",
gettext_noop ( gettext_noop (
"background process that executes outgoing wire transfers"), "background process that executes outgoing wire transfers"),
options, options,
&run, NULL)) &run, NULL))
{ {
GNUNET_free ((void *) argv); GNUNET_free ((void *) argv);
return 1; return GR_CMD_LINE_OPTIONS_WRONG;
} }
GNUNET_free ((void *) argv); GNUNET_free ((void *) argv);
return global_ret; return global_ret;

View File

@ -709,6 +709,13 @@ int
TALER_amount_round_down (struct TALER_Amount *amount, TALER_amount_round_down (struct TALER_Amount *amount,
const struct TALER_Amount *round_unit) const struct TALER_Amount *round_unit)
{ {
if (GNUNET_OK !=
TALER_amount_cmp_currency (amount,
round_unit))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if ( (0 != round_unit->fraction) && if ( (0 != round_unit->fraction) &&
(0 != round_unit->value) ) (0 != round_unit->value) )
{ {