[GNC-dev] gnucash maint: Multiple changes pushed
John Ralls
jralls at ceridwen.us
Thu Jan 10 19:42:41 EST 2019
> On Jan 10, 2019, at 12:44 PM, Geert Janssens <geert.gnucash at kobaltwit.be> wrote:
>
> Op donderdag 10 januari 2019 21:11:26 CET schreef John Ralls:
>>> On Jan 10, 2019, at 8:32 AM, Geert Janssens <geert.gnucash at kobaltwit.be>
>>> wrote:>
>>> Op donderdag 10 januari 2019 16:35:57 CET schreef John Ralls:
>>>>> On Jan 10, 2019, at 2:12 AM, Geert Janssens <geert.gnucash at kobaltwit.be>
>>>>> wrote:>
>>>>>
>>>>> Op donderdag 10 januari 2019 06:45:15 CET schreef John Ralls:
>>>>>>> On Jan 9, 2019, at 9:06 AM, Geert Janssens
>>>>>>> <geert.gnucash at kobaltwit.be>
>>>>>>> wrote:>
>>>>>>>
>>>>>>> Op woensdag 9 januari 2019 15:45:31 CET schreef John Ralls:
>>>>>>>>> On Jan 9, 2019, at 6:17 AM, Derek Atkins <derek at ihtfp.com> wrote:
>>>>>>>>>
>>>>>>>>> HI,
>>>>>>>>>
>>>>>>>>> On Wed, January 9, 2019 9:12 am, Geert Janssens wrote:
>>>>>>>>>> I like the idea of caching the system locale for future use. Too
>>>>>>>>>> bad
>>>>>>>>>> the
>>>>>>>>>> std::locale is working so poorly on Windows :(
>>>>>>>>>>
>>>>>>>>>> Op zondag 6 januari 2019 19:13:28 CET schreef John Ralls:
>>>>>>>>>>> +const std::locale&
>>>>>>>>>>> +gnc_get_locale()
>>>>>>>>>>> +{
>>>>>>>>>>> + static std::locale cached;
>>>>>>>>>>> + static bool tried_already = false;
>>>>>>>>>>
>>>>>>>>>> If I understand it correctly using static variables makes the
>>>>>>>>>> function
>>>>>>>>>> unsuitable for multi-threading, right ?
>>>>>>>>>
>>>>>>>>> Not necessarily. There is a race condition on first-use, but beyond
>>>>>>>>> that
>>>>>>>>> I don't see a MT issue here. The data is read-only, right?
>>>>>>>>> Multiple
>>>>>>>>> threads could read from the same read-only data simultaneously, so
>>>>>>>>> that
>>>>>>>>> should be fine.
>>>>>>>
>>>>>>> It was this first-use race condition I was referring to.
>>>>>>>
>>>>>>> Particularly, for thread safety I think setting tried_already = true
>>>>>>> should
>>>>>>> happen at the very end of the function, after we're sure cached has a
>>>>>>> proper value. Otherwise a competing thread could try to get the
>>>>>>> uninitialized cached value if thread execution of the first thread is
>>>>>>> interrupted right after tried_already is set true.
>>>>>>>
>>>>>>>>> Static data is ont MT-unsafe if it's being changed on a per-call
>>>>>>>>> basis
>>>>>>>>> (e.g. a time_t -> string API returning a static string buffer).
>>>>>>>>>
>>>>>>>>>> Any idea how difficult would it be to fix that ?
>>>>>>>>>
>>>>>>>>> You could add a mutex around the initialization. That's all I think
>>>>>>>>> you
>>>>>>>>> would need here.
>>>>>>>>>
>>>>>>>>>> I know GnuCash is not thread-safe by a long shot and gtk itself is
>>>>>>>>>> single
>>>>>>>>>> threaded so it doesn't matter that much.
>>>>>>>>>>
>>>>>>>>>> However I silently set a personal goal of changing that sometime.
>>>>>>>>>> The
>>>>>>>>>> C
>>>>>>>>>> code
>>>>>>>>>> is a lost cause IMO, but it might be worth to keep multi-threading
>>>>>>>>>> in
>>>>>>>>>> mind
>>>>>>>>>> as
>>>>>>>>>> we gradually convert to c++. In my basic understanding of threading
>>>>>>>>>> this
>>>>>>>>>> particular function should not be too hard to make tread safe.
>>>>>>>>
>>>>>>>> Right, and I decided against making this the first introduction of
>>>>>>>> mutex
>>>>>>>> into GnuCash. I think std::atomic
>>>>>>>> (https://en.cppreference.com/w/cpp/atomic/atomic
>>>>>>>> <https://en.cppreference.com/w/cpp/atomic/atomic>) is the correct
>>>>>>>> modern
>>>>>>>> C++ way for a simple case like this.
>>>>>>>
>>>>>>> I was hoping indeed modern C++ has some answer to this. This is my
>>>>>>> first
>>>>>>> foray into multi-threading by the way so I'm pretty green in this area
>>>>>>> and wishing to learn more about it.
>>>>>>>
>>>>>>> In this particular case would declaring the cached and tried_already
>>>>>>> as
>>>>>>> atomic be sufficient to make this thread safe ?
>>>>>>>
>>>>>>> It seems to me this would still allow multiple threads to go in and
>>>>>>> run
>>>>>>> the
>>>>>>> initialization code for setting cached. Given they all should end up
>>>>>>> setting cached to the same locale it's probably not dangerous to the
>>>>>>> application, only potentially running the same code multiple times
>>>>>>> where
>>>>>>> only once would be sufficient.
>>>>>>
>>>>>> My knowledge of concurrency is pretty dated. I had an operating system
>>>>>> course 30 years ago that included a discussion of concurrency, and some
>>>>>> parts of Glib are thread-safe, so I’ve bounced up against it doing
>>>>>> maintenance a few times. Just mutex and semaphore stuff.
>>>>>>
>>>>>> I haven’t even yet read the concurrency chapter in Josuttis, but my
>>>>>> understanding of std::atomic is that a std::atomic variable magically
>>>>>> eliminates races and has certain operations (construction and operator=
>>>>>> among them) that are guaranteed to be, well, atomic: The compiler will
>>>>>> always create a contiguous uninterruptible sequence of machine
>>>>>> instructions
>>>>>> for atomic operations. There are also ways to order execution of some
>>>>>> functions with std::memory_model that can make locks (i.e. mutexes and
>>>>>> semaphores) unnecessary, and the compiler is able to figure out when
>>>>>> that’s
>>>>>> true and when it isn’t and to take care of the locking without the
>>>>>> programmer needing to explicitly code it.
>>>>>>
>>>>>> It’s not something I think worth worrying about now. GnuCash is very
>>>>>> far
>>>>>> away from being multi-threaded. Besides, the natural home for the
>>>>>> instantiation of the cached C++ local is in main(), after all of the
>>>>>> environment options are parsed but before the GUI is loaded or the
>>>>>> first
>>>>>> session started. This isn’t the function to worry about concurrent
>>>>>> access.
>>>>>
>>>>> Fair enough. Though I'd think it should happen in libgnucash
>>>>> initialization at some point rather than in the gui code. But that's
>>>>> just
>>>>> a matter of sorting out all startup configuration we do at some point
>>>>> and
>>>>> estimate which part is needed for libgnucash and which part is only for
>>>>> the gui code.
>>>>
>>>> Um, I don’t think it’s right to consider main() part of “the GUI code”.
>>>> It’s the mandatory starting function for any program.
>>>
>>> Except when we want to interface with libgnucash from binding languages
>>> such as python or guile directly. At least in this context there's no
>>> main() that can explicitly set our libgnucash environmental requirements
>>> (like environment variables from the environment file or locale etc) that
>>> I know of.
>>>
>>> GUI was indeed a vague term to explain this.
>>>
>>> You could argue the init function itself could be exported so the bindings
>>> can call that as well, making the calling script the equivalent of the
>>> main() function. That would be a valid option.
>>>
>>>> There’s at present no
>>>> “libgnucash initialization” at runtime unless you mean
>>>> libgncmod_engine_init. We can make one, of course, or extend
>>>> libgncmod_engine_init, but that’s still going to be called from main()
>>>> (as
>>>> libgncmod_engine_init is now, via gnc_module_init) and should happen
>>>> before
>>>> a future thread-enabled GnuCash starts spawning threads.
>>>
>>> My message had many implicit assumptions. I'm aware there's no formal
>>> libgnucash initialization yet. This was thinking out loud about the
>>> future, a future libgnucash where we no longer depend on gnc-module
>>> (which is one of our long-term plans).
>>>
>>> At that point we'll need some form of libgnucash initalization and yes,
>>> libgncmod_engine_init would be a first potential candidate.
>>>
>>> A more radical idea (not fully analyzed by any means) could be to build up
>>> libgnucash as a class hierarchy in which the constructor of the top-level
>>> class does all the more general initialization (like configuring the
>>> environment and locale).
>>> In that case initialization would happen automatically at first
>>> instantiation of that class. That could happen in a main() function for a
>>> C++ program (like future GnuCash proper) or from a binding script.
>>
>> I don't think that writers of other programs would be happy to have
>> libgnucash doing its own localization determination separate from their
>> program, and especially not changing the environment behind their backs.
>> The portable way for localization is for the calling program to do whatever
>> it wants to set the locale in the environment and for dependencies to read
>> the environment if they need to. That's not to say that we couldn't move
>> our localization code to core-utils and expose it in the libgnucash API,
>> but it shouldn't happen automatically in case the application wants to do
>> something different.
>
> I don't think what I am trying to convey would do anything you worry about.
> Let's refine my thoughts a bit more then.
>
> I would like libgnucash to "just work" by default and not force calling code
> to do initialization unless they want to deviate from that default. That means
> reading necessary bits from the environment, which we only want to do once. So
> it makes sense to do so at the very first use of anything libgnucash in my
> opinion.
> I see similar things in for example libgtk. It behaves sensible by default but
> it can be tweaked by means of a settings.ini file or some environment
> variables.
>
> GnuCash as is currently stands already depends on environment in several ways:
> locale is one, paths to dbi drivers is another, as are load paths for guile.
> Most of these will also be required to make libgnucash function properly when
> we are ready to split it off. So I think it would be useful if libgnucash
> could manage a default configuration for these things with an exposed
> interface to allow callers to override these (be it via a configuration file,
> environment variables, direct function calls,... to be detailed).
>
> So the idea is that a properly installed libgnucash in a well defined
> environment is able to initialize itself. If overrides are needed, calling
> programs should be able to provide those. That should make setting
> configuration of libgnucash optional in the best case and easy in the worst.
>
> Note my idea of implicitly initializing on first use probably implies any
> overrides should be set in some way *before* that first use. So either in a
> config file, via the environment or via optional parameters to the
> constructor. The latter is probably not very feasible.
>
Gtk has gtk_init. You call it, set up your GUI, then call gtk_main to start the event loop. libguile has several initialization functions to choose from: https://www.gnu.org/software/guile/manual/html_node/Initialization.html#Initialization.
No "initialize on first use" there.
Consider that "initialize on first use" means that *every* exported function has to have a clause or belong to a class whose constructor checks for and initializes the global state. If we just need to do it for localizing the C++ UI locale, that's what get_cpp_locale() does and we're done, except that we'd have to make that first call thread safe. If we need to do much more, a library init function is cleaner and simpler... and might be cleaner and simpler than making get_cpp_locale thread safe.
This is all for the rather distant future, though, so let's defer figuring it out til then. We'll forget by then anything we decide now. ;-)
Regards,
John Ralls
More information about the gnucash-devel
mailing list