Closed Bug 26291 Opened 25 years ago Closed 3 years ago

string bundle shouldn't be using using OpenInputStream(), block UI thread

Categories

(Core :: Internationalization, defect, P3)

All
Windows NT
defect

Tracking

()

RESOLVED INACTIVE
Future

People

(Reporter: jud, Assigned: alecf)

References

Details

(Keywords: perf)

Attachments

(7 files)

http://lxr.mozilla.org/seamonkey/source/intl/strres/src/nsStringBundle.cpp#274 
shows a line using the netUtil function that wraps an OpenInputStream() call. 
The result is that the user is handed an input stream to read data from. The 
inputStream->Read() call is a blocking call and can/will block the thread it's 
called on. If this steam is guaranteed to be read from a thread other than the 
UI thread, evaluate whether or not the blocking behavior is ok, and if so, close 
this bug. Otherwise, your code need to do an asynchronous load of the URI. If 
you don't want to implement nsIStreamListener, please look at 
nsIStreamLoader.idl which will call a callback function you implement when all 
the data has arrived (it does all the asyncronous work for you).
OK, this is pretty serious, and is causing bug 26265. The profile manager is 
trying to load a string bundle on startup, but because of this attempt at a 
synchronous load, no data is loaded from the .properties file.
No longer blocks: 26265
Priority: P3 → P2
Target Milestone: M14
If someone needs help understanding an async load, just let me know.
Hmm..., that would be me!

Jud,

Would you give an instruction of how this is going to work? I imagine that the 
stringBundle would pause after requesting an async load of the property file 
and wait to be called when the stream is available. Is this the way it works?

Also, would you point me to a sample code for reference?


Thanks
Status: NEW → ASSIGNED
Blocks: 26482
OK, if there is a rule that states that nobody is allowed to block on the UI
thread, then that must mean that XUL files are also loaded on a different
thread. Then the UI thread spins in its event loop, and when the XUL file has
finished loading (or has loaded partially?), the XUL loader sends an event to
the UI thread's event loop, which then processes that data.

We will probably need to do something similar for StringBundle. We could have
the caller of the StringBundle set up a callback, similar to nsIStreamLoader's
callback mentioned above. StringBundle would itself implement nsIStreamLoader's
callback, which would call the app back, which can then do whatever it needs to
do with the StringBundle (e.g. getting strings from it). This adds a bit of
complexity to the caller, since they can't just get a bundle and then get
strings from it. Oh, well. I guess we can't get around this limitation due to
our UI thread policy.
I'm seeing this on linux,too.

try running -installer on linux, and you see the problem.  if it is happening,
you'll see dump statements that say:  "please fix bug 26291" and buttons will
say "Migrate *" and "New Profile *" instead of what's in the string bundle.
http://bugzilla.mozilla.org/show_bug.cgi?id=26265 contains more comments from
Jud and Erik.

For now, we do not seem to have a concrete solution for the async loading.
Unless the problem on Mac and Linux persists after my patch for
26265, I am pushing it to M16. 
Target Milestone: M14 → M16
No longer blocks: 26265
Blocks: 31804
Blocking stream loading in UI therad is a known problem in Seamonkey. Unless
we plan to address them all, I will not fix this particular problem in this
release.
Status: ASSIGNED → RESOLVED
Closed: 24 years ago
Resolution: --- → REMIND
just be aware that if this bug is causing profile manager misbehaviour on Mac, 
we will have to investigate another method for JS localization. 
QA Contact: teruko → tao
.
Status: RESOLVED → VERIFIED
I just saw the mail from tao on this - and noticed this is closed as VERIFIED
REMIND... should we reopen it, and at the very least mark as Future?

So how is this expected to work? the string bundle API is not asynchronous, and
I don't think it should be. It sounds like we basically need to start a url load
when someone calls nsIStringBundleService.CreateBundle(), and then block
nsIStringBundle.GetStringFromName() until the bundle is loaded.
yes, let's reopen it. Should we discuss this in the newsgroup or in this bug report?
Status: VERIFIED → REOPENED
Resolution: REMIND → ---
When we originally designed the String Bundle API, we thought that it would be
useful to allow the caller to specify a URL for the property file. This is
basically at the root of this problem. Perhaps it is not so reasonable to open
a separate TCP/IP connection just for a property file. Perhaps apps would be
packaged as JAR files that contain XUL/DTD/JS/property files, in the same way
that Java applets are often packaged in zip/jar files.

Perhaps we can agree to make the String Bundle less general by allowing callers
to only refer to the contents of such packages. E.g. jar: URLs only, or also
allow file: URLs...

Just throwing some ideas out there...
no, I think specifying the url is reasonable.... and it doesn't preclude us from
loading it asynchronously.. 

the trick is basically to make sure there's enough time between the
CreateBundle() call and the GetStringFromName() call to allow the
bundle to load asynchronously. This mainly involves fixing callers to  make sure
there is enough time between the two calls
(for instance, the CreateBundle could happen in the onload= handler, and the
GetStringFromName could happen during normal processing)
That sounds good to me.
I have a patch sitting in my local tree (MN6 branch though) does something
similar to what is suggested here and a few others:

  1. I added a new api

     nsIStringBundle CreateAsyncBundle([const] in string aURLSpec);


   to nsIStringBundleService. This new api returns a bundle that loads string
   resource asynchronously.

  2. I added to nsIStringBundle:

    +  // init version
    +  nsStringBundle();
    +  nsresult Init(const char* aURLSpec);

  The default contructor does only basic data member initialization and leave
  the real bundling to init() which returns nsresult.

  Also the strinbundle now becomes an observer of StreamLoader. In Init(), it
  opens the stream loader and waits for OnStreamComplete to be called. When
  that happens, it loads the bundle and notify its observer (from JS/C++) or
  registered callback (from C).


To use this new scheme, the caller need to wait until the notification comes
or check the status of the stream loading via a new api, GetLoaded().

In JS environment, the async bundle creation, as alecf suggested, should be
somewhere in onLoad() or the like. In C++ object, Init() or constructor is a
good place to initialize the async bundle.

How does this sound?


I'll attach the patch later. (This patch contains other works, though)

Status: REOPENED → ASSIGNED
no, I think that is the wrong approach

To reiterate:
1) we should not be adding new APIs - the existing CreateBundle should switch to
an asychronous model
2) The creation of the string bundle via CreateBundle() should kick of the URL
load
3) when someone calls GetStringFromName(), then we should block until the string
bundle is done loading. (and if the string bundle is already loaded, this will
return immediately with the new string)

alec's got it.
OK, blocking the calling threading until property file is loaded sounds
reasonable.


Jud,


What's the safest way to block the calling thread until OnStreamComplete() is
called and executed?


thx
something like...

::CreateBundle(){
...
nsAutoMonitor mon(mMon /*create a monitor in the bundle's init method*/);
nsIStreamLoader loader;
NS_NewStreamLoader(getter_AddRefs(loader), mURI, this /*the load observer*/);
mon.Wait(); // this blocks the current thread.
...
}

The above will kick off an async load of mURI, your
nsIStreamLoaderObserver::OnStreamComplete() impl will be called once all the
data has arrived (or in case something went wrong, be sure to check the result
code in that method callback). If you want to gather status, bytes transfered,
etc. you should use nsIStreamListener|Observer instead of
nsIStreamLoaderObserver. Once OnStreamComplete() is called you can suck all the
data out of the stream.

::OnStreamComplete(...) {
...
nsAutoMonitor mon(mMon);
// suck all teh data out of the stream
mMon.Notify(); this will unblock the CreateBundle() call's thread.
...
}

I like alec's suggestion of lazily blocking/sleeping the caller's thread only if
all the data hasn't arrived yet, but that adds a level of complexity, and we're
more vulnerable to race conditions, thus, I'd just block everytime in
CreateBundle().
Jud, I'm just trying to understand your concern about not waiting for the URL
in CreateBundle(). The app currently loads many different types of URLs
asynchronously, and it doesn't wait for them. E.g. HTML docs, images, etc. Are
you saying that StringBundles are fundamentally different, so they shouldn't
be treated the same way? For example, are you concerned that StringBundles might
be used from the very thread(s) that are doing some of the related work, so that
there might be deadlock?
my original suggestion was to have string bundles load data asynchronous, but I
received a ton of resistance. Apparently most string bundle users aren't setup
to consume data asyncronously (ie. string bundles are used all over the place
and therefore we can't go in and change the control flow of all the usage sites).
I may be missing something, but why would we need to change any of the callers
when we make CreateBundle() load the URL asynchronously? The callers are
currently calling CreateBundle() and then GetStringFromName(). In the new world,
they would continue to call these methods. The only difference would be that
CreateBundle() returns immediately, while GetStringFromName() might block,
depending on whether or not the bundle has arrived at that point in time. I
guess I was assuming that CreateBundle() would spin a new thread and load the
URL in that thread. GetStringFromName() would then synchronize with that thread,
so there shouldn't be any deadlock worries. What am I missing?

Of course, to take advantage of this new design, the callers ought to call
CreateBundle() at start-up time or soon thereafter, especially if they expect to
make use of the bundle. Callers that might not ever use the bundle may wish to
load lazily, waiting until some situation arises before calling CreateBundle().
Of course, these users will then have to wait for the bundle to arrive. That's
the penalty they pay for loading lazily.
I think we should eventually make CreateBundle() return immediately and have
GetStringFromName block, but I think jud's suggestion is a good first step... 
"I may be missing something, but why would we need to change any of the callers
when we make CreateBundle() load the URL asynchronously?
"

If you bury the async load using wait/notify on monitors behind the createbundle
impl, and onstreamcomplete, you don't have to change callers. There's some
communication issue here, not sure what it is. if you want to provide a truly
async model (no blocking/waiting), you have to change all the call sites. using
nsIStreamLoaderObserver allows you to get around this (though it's not *really*
async as you're blocking the thread). This bug is about getting rid of the sync
api usage of necko; and nsistreamloaderobserver will do that. I'm not familiar
w/ the bundle api or it's usage, if someone wants to make sure the wait/notify
can occur (maybe it's  a no-brainer, i was just expecting other methods off the
string bundle being called and the order wouldn't be known) w/ the correct
waiting, great. My point is that whether you block in CreateBundle, or Get*(),
because the calling code is synchronous, it really doesn't matter, and you could
avoid any race cond. issues by just blocking in CreateBundle.
How about using a condition var in createbundle() and check the state change
in GetStringFromName()? Will this work?

Jud's suggestion sounds workable. But, blocking the thread in createbundle()
seems to defeat the advantage of async laoding.

I'll try both and let you guys know what tunrs out. Thanks for the valuable
input.
again, this change isn't happening to gain the advantages of async loading (it's
just not possible w/ out a ton of rewrite of string bundle consumers). This
change is to avoid a broken api. Changing to the new api will do nothing but
cause the string bundle code to use a supported api (and one that's proven to
work). The condition var was what I was thinking about using, but I chickened
out because you'd have to do atomic lock/unlocks which aren't cheap, *and*
because things aren't really async anyway, there's no gain to moving the
blocking code out of CreateBundle().
IMO, we should not try to change str bundle's usages. Instead, either hide the
synchronization in current APIs or provide a set of new APIs (as I originally
planned) as an alternative to the callers that *really* need them. While the
former will affect all current usages, the latter should affect only new usages
or serve as a fix to problems caused by using the OpenInputStream() version.
Adding wtc to the CC list.
I would prefer not to add new methods to the APIs. I like Alec's idea of
starting with Jud's suggestion (make CreateBundle() block), moving to the other
idea later (CreateBundle() returns immediately, while GetStringFromName() may
block).
this really isn't that complicated! If someone doesn't attach a patch this soon, 
I'm going to take this bug, implement it, and show you all how easy this is :)
I don't think anyone's struggling w/ the work, we're just bickering over what we
want.

Tao/Erik, if we want a new async API, sounds great. We might be able to get new
folks to use it and benefit from the fruits of async operations. I wouldn't
spend time here though unless we do the extra work of determining how usages can
be restructured (pass control flow out to the innermost event loop) to take
advantage of it.

As for the "where to block" impl detail, Erik, I suspect the majority of
CreateBundle() usages look like this
http://lxr.mozilla.org/seamonkey/source/extensions/cookie/nsCookie.cpp#205 , and
note that the GetString() call occurs on the same thread, right after the
CreateBundle call. Therefore, blocking in GetString*() has *no* gain and adds an
atomic lock/unlock overhead on some condition var (if I'm wrong here let me
know), as well as the potential for a race condition if a new
GetStringFromJud'sHouse() method is added and the lock is misplaced or
forgotten. Of course, you'd know more about the usage landscape of strings, and
maybe we have instances of CreateBundle() being called on one thread (then that
thread drops into an event loop), while the GetString*() is called on another
thread.
I wouldn't work on a new async API (yet), if I were the one assigned to this.
It's up to you, Tao.

As for current usage patterns, I wouldn't be at all surprised if a lot or even
most of the call sites called GetStringFromName() right after CreateBundle().
However, returning immediately from CreateBundle() would allow callers to call
that during start-up, so that GetStringFromName() might return quickly too,
depending on when it is called.

I think I've said what I wanted to say, several times now. Over and out.
erik, I hear what you're saying, but "returning quickly" doesn't matter if the
two calls are made in sequence on the same thread.
Jud's suggestion of blocking in CreateBundle() seems to be safer and
accepted by all. I am going to implement that first. Although, I like Alec's
idea of blocking in the GetStringFromxxx() since it has the potential of
performance gain from async loading. I will look into alecf's idea in the
background.

As to the status of the patch, I believe that the only thing left is to
implement Jud's pseudo code in the CreateBundle() & onStreamComplete().

I probably will also attach the new async apis version for later reference.
It is based on the MN6 branch, though.
well, in C++, we often do a CreateBundle/GetStringFromName quickly in sequence,
but in JavaScript, there I've noticed this pattern all over:

var bundle = srGetBundle(...)

function foo() {
   window.alert(bundle.GetStringFromName(...));
}

This pattern will benefit from async loading when blocking in GetStringFromName()

Also, Ben Goodger has a new <stringbundle/> XUL tag implemented in XBL, which
will call CreateBundle at initialization time (actually, when the CSS is
evaluated, which is after the onload handler)

and then you can say

function foo() {
  window.alert(document.getElementById("mybundle").getString(...));
}

this pattern also benefits.. I have a feeling that we do alot more
localization-related stuff in JS anyway, so I think there are more of these
patterns than the one that Jud just pointed out.
Blocks: 62177
hi, Jud:


Is nsIStreamLoader truely async? I try to block the calling thread in
createBundle() as suggested and found that Mozilla hangs at mon.wait()
and never wake up after :-\

yes, it's async. One of two things must be happening.

1. streamloader::Init() is failing. is the return code from your streamloader
creation ok?
2. you're not getting your OnStreamComplete() callback hit. Do you see it get
called?
>yes, it's async. One of two things must be happening.

> 1. streamloader::Init() is failing. is the return code from your streamloader
> creation ok?

I assume it is since it is the same code segment from the first attached
patch (20370)which was tested against MN6 branch.

I'll double check.

> 2. you're not getting your OnStreamComplete() callback hit. Do you see it get
> called?

No. This was the first thing I checked. I'll look into it further once I got a
turn-around.

regarding the 12/08/00 15:17 patch.

nsStringBundle::Init() - please use a nsCOMPtr for the uri variable. Also use on
for the loader variable (which is currently leaked).

::onstreamcomplete() - not sure why you're releaseing aLoader. Looks like you're
compensating for the leak in the Init() method. You don't need to do this
though.

mLoaded - Not sure why you need this member var if you're blocking in
CreateBundle().

I'm not sure what you're doing w/ the nsIObserver stuff. Is that seperate from
the async loading?
>regarding the 12/08/00 15:17 patch.
>
>nsStringBundle::Init() - please use a nsCOMPtr for the uri variable.
> Also use on for the loader variable (which is currently leaked).

I was mimicing NS_NewStreamLoader()'s usage in nsXULDocument.cpp.

>::onstreamcomplete() - not sure why you're releaseing aLoader. Looks like
> you're compensating for the leak in the Init() method. You don't need to do
> this though.
same reason above. I'll correct it.

>mLoaded - Not sure why you need this member var if you're blocking in
>CreateBundle().
That patch was done before adopting your suggestion of blocking in
createbundle(). That's why you don't see mon.wait() or mon.notify() in that
patch. The caller needs to call bundle->GetLoaded() to check if the resource
bundle is complete.

> I'm not sure what you're doing w/ the nsIObserver stuff. Is that seperate
> from the async loading?
The observer stuff is to notify the caller when the stream loading is complete.
It's like what StreamLoader does. With the implementation in this patch, the
caller gets to choose how the string bundle is loaded: synchronuously or
asynchronously. The caller calls CreateAsyncBundle() to 'benefit' from  async
loading.


Jud,

>1. streamloader::Init() is failing. is the return code from your streamloader
> creation ok?

Just to confirm that the return status of streamloader creation is OK. But,
none of the streamloader's member funtions are called after mon.wait().

you should break in streamloader's OnStopRequest() method to see if it's even
being hit.
I inserted printf in all of StreamLoader's member functions and the only one I
got is nsStreamLoader::Init() which returns '0'. In VC++ debugger, none of the
other member functions got called.
well, I'm a moron for two reasons:
1. I didn't recall the fact that nsIStreamLoader requires an event loop on the
calling side; and I wrote the stupid thing.
2. I pushed us down this road for days w/ out realizing it. 

I'm sorry.

nsIStreamLoader is a simplification interface so you don't have to muck w/
nsIStreamListener|Observer to do asynchronous I/O. It *doesn't* allow the caller
to get the async i/o benefits w/ out dropping into an event loop (duh! that
would redefine the notion of "async" :-/). Unfortunately, there are many string
bundle users that make top level string bundle calls synchronously (CreateBundle
or GetSTring*). Without reworking those callers to handle string loads via
asynchronous callbacks (prolly a boat load of work), they cannot be helped by
nsIStreamLoader|Observer(). 

Result: nsIStreamLoader is useless here, unless you want to provide *new* async
i/o functionality for string bundle users.
wait, so why can't we use the nsIStreamLoaderObserver mechanism?
because nsIStreamLoaderObserver is just nsIStreamListener|Observer underneath,
and that requires the calling thread to process events (which it obviously isn't
doing if we've mon.wait()ed the thread :-/).
Jud,

Per your comments, should we take the patch which you reviewed earlier?
This patch requires caller explicitly call CreateAsyncBundle() to load
resource bundle asynchronously. We will notify the caller upon loading
completed instead of blocking thread in StringBundle.

The callers of the new api will use the new api when the sync version does
not work well for them (in their env.)
hmm, well. now that I understand a little more about what you're using topics
for, I'd suggest just using nsIStreamListener|Observer to do the notification
(the topics nsIObservers aren't needed). Also, it's not clear to me how that
test works because it doesn't even have an event loop (which is required for
asynchronous operations).
Hi, Alec, Jud:


Would you review and sr the aptch attached above? To sum up:

 1. Code cleanup.
  * I removed the old style constructor of nsStringBundle() and use Init() or
    InitSync() to return the status/error code.
  * Use nsCOMPtr as suggested.

 2. New apis.
  * I add a new api, CreateAsyncBundle(), to nsIStrinBundleService so callers
    have an option to load the resource asynchronuously. CreateAsyncBundle()
    returns right away after creation of the new bundle and nsIStreamLoader.

    It will notify the caller upon completion of resource loading. In C++/JS,
    the caller should register as an observer of NS_STRBUNDLE_LOADED_TOPIC.

  * Added a new api, RegisterCallback(), to nsIStrinBundle for C caller. The
    callback will be called upon the completion of stream loading.
  * Added a new attribute to nsIStringBundle so callers can test if the resource
    binding is complete.

No current callers of strres need to change their usage unless they want to
take advantage of async loading or the sync version does not work well for them.

thx
Whiteboard: patch ready for r/sr
The patch looks good, a few things though:
- you should never directly compare anything to NS_OK, you should use
NS_SUCCEEDED() (I know the old code was already there, but since you're touching
that line, you might as well fix it)
- instead of using a callback, which is not scriptable, you should use the
nsIObserver interface, and notify callers via nsIObserver::Observe (note that
this does not imply going through the observer service)

honestly, I'm doubtful that anyone is ever going to use this... it's going to be
so much work just to be notified that a string bundle is loaded that everyone is
going to be lazy and just use the non-async version. Not to mention the
complexity it adds to every call site of CreateAsyncBundle - the value gained
from loading the bundle asyncronously seems lost to me just in the amount of
code you have to write and maintain just to make sure that it's loaded.

How much work would it be to put an event loop in the string bundle code so that
it can pump the events to load the string bundle? I still see value in that..
basically we make CreateBundle() return immediately (like we talked about
before) and then wait for the bundle to load using nsIStreamLoaderObserver.. if
the bundle still hasn't loaded when GetStringFromName is called, then we pump
the event queue until it arrives.
gotta love soul searching discussions regarding async operations, coupled w/
observer notification callbacks :-).

I'm skipping a code review until we resolve what we want (I suspect Tao's about
to INVALIDATE this bug if we go through another iteration of what it "should" do
:-)).

The bug was originally borne out of the use of a potentially broken API. I was
suggesting that we convert callers over to use an async API instead. I got shot
down quickly on that as string bundles are used at low levels and, I
think/thought, before we even have an event loop up in some cases. The bug was
left open in hopes that necko would provide honest sync i/o at some point
(hasn't happened).

If we're not going to convert old usage of sync string bundles to the new api
provided by Tao, or we don't have a scenario that can help drive the new api
usage, we should probably put this bug to bed.

alec, you commented that the win is lost w/ the overhead incurred w/ the new
async api impl. It may have room for optimization (I haven't looked), but the
whole idea is that the *current* model blocks the thread. Blocking a thread
versus slow/heavy code are two different things and I'd vote for the async api
just to get rid of the current blocking scenario.

Regarding the event loop addition... I like it! :-). We could use Tao's original
nsIStreamLoaderObserver patch w/ slight modifications (I've said that before
:-/). I would still make CreateBundle block though for two reasons:
1. I still argue that the callers aren't async anyway, so they're going to block
somewhere at sometime.
2. To package an event loop in the string bundle code means we're sticking a
while(1) in somewhere, and it might as well be in the root call (CreateBundle())
so we can avoid race conditions and flag member variables.

Here's what you'd replace the mon.Wait() call w/ 

...
NS_WITH_SERVICE(nsIEventQueueService, eventQService, kEventQueueServiceCID,
&rv);
if (NS_FAILED(rv)) return rv;
 
// create is only necessary if there's the potential that 
// CreateBundle() is being called before anyone else has 
//created a queue for the thead (I believe this is the case)
rv = eventQService->CreateThreadEventQueue();
if (NS_FAILED(rv)) return rv;
 
eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, &eventQ);

while(mRunning) {
  PLEvent *gEvent;
  rv = gEventQ->WaitForEvent(&gEvent);
  //if failed return
  rv = gEventQ->HandleEvent(gEvent);
  //if failed return
}

stringbundle::OnStreamComplete(...) {
  mRunning = PR_FALSE;
....
}

...
i'll try it out when I got a trunaround later... thx
OK, here is the proposed async version of streamloading. I tried it out on both
win32 and linux. I haven't got a Mac build to test it, yet.

However, I might be halucinating, I think it is a bit slower than the sync
version. Therefore, please give it a try and let me know if you observe the same
thing. Since the sync apis still there, we can switch back and forth at will,
I'd like to check it in asap so Alec and I can add more stuff to strres module.

Let me know what you think?

thx
processing events isn't free. it may very well be slower. any chance you can
quantify it w/ printfs and times? Slow or not, we're now no longer blocking the
main thread (or any thread using string bundles for that matter) which is a
*good* thing.
Measuring the loading time could only give us the information about strres bundle.
I am wondering if the introduced eventqueue stack would slow down tasks on other
thread.

To make the swtiching (sync v.s. async), I add a flag which is set via env. var.
I am attaching it below.
now I'm having second thoughts about this (don't kill me tao :-/). So, the async
load indeed incurs overhead. If string bundles are ever planning on accessing
remote bundles (on a server, nfs or otherwise) the async impl makes sense. But,
if the data is always local (is it?) maybe we should stick to sync?
theres alot here that I don't understand:
- are you allowed to call PR_GetEnv from a global scope like that? I'm surprised
that compiles :) - in any case, you should do it from the string bundle
service's constructor.
- why are you not always addref'ing on return of getStringBundle()? We should
always be addrefing the bundle before giving it back to the caller
- I thought we had agreed that we would not be doing a seperate API - if this is
true, why do we have callbacks? I'm glad to see the user of the observer
mechanism, but it seems like we should instead drop the c-function style
callback mechanism and instead just use the nsIObserver interface, and not go
through the observer service. (i.e. have a
nsIStringBundle::registerListener(nsIObserver *aListener))
But again, I don't think we should have a seperate API.
- we should not be flushing the string bundles when we do a cache flush, those
should be seperate operations.. and again I don't see any reason to go through
the observer service.
>now I'm having second thoughts about this (don't kill me tao :-/). So, the async
>load indeed incurs overhead. If string bundles are ever planning on accessing
>remote bundles (on a server, nfs or otherwise) the async impl makes sense. But,
>if the data is always local (is it?) maybe we should stick to sync?
Most likely, it will be local. Even in the remote case, the property files
are packaged in *.jar.

The benefit of using async occurs when you know you definitely need str bundle
and request the bundle in something like JS onLoad() or C++'s object
constructor. When you call GetStringFromXXX(), mostlikely, the bundle is ready.

That's what I said, keep two sets of APIs and leave the call to the consumers.


> theres alot here that I don't understand:
> - are you allowed to call PR_GetEnv from a global scope like that? I'm
> surprised that compiles :) - in any case, you should do it from the string
>  bundle service's constructor.
It's a quick hack before we are sure the eventqueue blocking async loading is
really what we want. It's a defect as it stands now; I'll move it to the constr
as suggested.

> - why are you not always addref'ing on return of getStringBundle()? We should
> always be addrefing the bundle before giving it back to the caller
I am always addrefing it.. In the async case, I did it right after the creation
of the new bundle (set addre to true); otherwise (addref == false), it will be
added before return.
> - I thought we had agreed that we would not be doing a seperate API -
In fact, there could be 3 sets of apis:
  1. sync (OpenInputStream()) -- the old style
  2. async w/o blocking -- true async: StreamLoader + observing consumer)
     I need add a new api to reuse part of current patch.
  3. async but blocking in Init() -- transparent to current consumers. This is
     what you are seeing now in the patch. The observer/callback stuff are not
     necessary for this.

We all like (3), but, it turns out there is an overhead for doing blocking
async. So, I am leaving the consumer a choice; use async when they can,
otherwise, stay with the sync one.

I will explain the rest later; I am late for my apt. BUt, could we get the
current patch in; we will decide how to deal with the non-used part of the patch.

thx
> - I thought we had agreed that we would not be doing a seperate API -
> if this is true, why do we have callbacks?

> I'm glad to see the user of the
>  observer mechanism, but it seems like we should instead drop the
> c-function style callback mechanism and
The C-style callback is for consumers using async loading (2) from C function.
Mailnews has such example:
http://lxr.mozilla.org/mozilla/source/mailnews/mime/cthandlers/vcard/mimevcrd.cpp#1974

> instead just use the nsIObserver interface, and not go
> through the observer service. (i.e. have a
> nsIStringBundle::registerListener(nsIObserver *aListener))
Is there a strong reason (great benefit) of using registerListenser mechanism?
The nsIObserver approach is easy to implement in strres and probably as easy to
implement for the consumer. Besides, I don't need to introduce another new
intereface.
> - we should not be flushing the string bundles when we do a cache flush,
> those should be seperate operations.. and again I don't see any reason to
> go through the observer service.
I will take out the notification part in flushcachebundle().



Whiteboard: patch ready for r/sr → patch reviewed by alecf
In lacking of another review on the patch, I am marking this bug as remind. I
will submit another patch to change the nsStringBundle creation so that the
constructor does only initialization and a new function, Init(), does the
loading and bundling stuff.
Status: ASSIGNED → RESOLVED
Closed: 24 years ago24 years ago
Resolution: --- → REMIND
OK, I got a last minute review from erik based on discussion with danm.
reopening and checking it in.
Status: RESOLVED → REOPENED
Resolution: REMIND → ---
fixed checked in!
Status: REOPENED → RESOLVED
Closed: 24 years ago24 years ago
Resolution: --- → FIXED
Whiteboard: patch reviewed by alecf
.
Status: RESOLVED → VERIFIED
*** Bug 76682 has been marked as a duplicate of this bug. ***
re-opening as perf. Pete (guy who just duped another bug w/ this one) mentioned
he's seeing a couple second bottleneck in here on startup. cc'ing him.
Status: VERIFIED → REOPENED
Keywords: perf
Resolution: FIXED → ---
Are you suggesting not to use OpenInputStream() which is what people get unless
they "setenv STRRES_ASYNC 1".
OpenInputStream() blocks the UI thread which is the perf issue that pete says
he's seeing.
I've waded through the labrynth of funct calls to here

nsNetUtil.h:

NS_OpenURI

ioService->NewChannelFromURI

Is where i see the hiccup begin. I'm still investigating.

--pete

 
Well, i wrapped the beginning and end of 

nsIOService::NewChannelFromURI  

w/ some printf's:
nsIOService::NewChannelFromURI

printf("BEGIN: NewChannelFromURI\n\n");
. . . 
printf("END: NewChannelFromURI\n\n");

Which in turn i wrapped the caller in NS_OpenURI

    printf("\n\nHRMMM 1. . .\n\n");
    rv = ioService->NewChannelFromURI(uri, &channel);
    printf("\n\nHRMMM 2. . .\n\n");


My output is this:

#
#
#
************ NS_OpenURI NEW!! ************
#
#
#


HRMMM 1. . .

BEGIN: NewChannelFromURI

BEGIN: NewChannelFromURI

END: NewChannelFromURI

BEGIN: NewChannelFromURI

END: NewChannelFromURI

BEGIN: NewChannelFromURI

END: NewChannelFromURI

BEGIN: NewChannelFromURI

END: NewChannelFromURI

BEGIN: NewChannelFromURI

END: NewChannelFromURI

BEGIN: NewChannelFromURI

END: NewChannelFromURI

END: NewChannelFromURI



HRMMM 2. . .





************ OUT #### NS_OpenURI NEW!! ************


This seems like erratic output. 

So i thought this looks a bit strange.

--pete
Hi, Jud:


One way to fix this problem is to use non-blocking async bundling: 

  1. caller "new" an async bundle which kicks off an async loading and
     register itself as an observer of this loadingCompleteTopic; 
     non-eventloop is involved. 
  2. the caller runs off to do something eles
  3. in the meantime, the stringbundle works on property file loading and 
     bundling. When the bundling is done, StringBundle notifies the caller
     that bundle is ready for use.
  4. Caller calls bundle->GetStringFromName("foo").
 
The drawback is that should the caller needs the string before it is ready,
some fallback mechanisms are desired to prevent runtime error.

I've said this a million times - I still think this is too complicated for the
caller. Nobody is ever going to use that API...

The real way this should work is this:
- caller creates string bundle
  - string bundle kicks off load in the background
- caller requests a string
  - if the bundle hasn't finished loading, the string bundle manually pumps the
events until the bundle is loaded
  - bundle returns the string

I'm 100% for your proposal. But, is there a clean way blocking the calling
thread without introducing overhead? If yes, feel free to take this bug over.

If not, could we attack such bottleneck problems case by case instead of 
one cure for all? One obvious example is using XUL caching to lazily load 
bundle and prevent reloading the same bundle.
the clean way is to put an event loop in there, but if jud knows of a way to
process just a single kind of necko message, that would be better...

as for caching, we already do caching of string bundles - it sounds like what
pete is encountering is the load of the first string bundle..

pete, the bundle has to be loaded SOMETIME, so really it's just a matter of
figuring out if we can offload it onto a seperate thread or not.

do you know what .properties file file is being loaded?

taking this bug, one of these days I'll fix this, just not now
Assignee: tao → alecf
Status: REOPENED → NEW
Target Milestone: M16 → mozilla1.0
Yea, Alec, I'm looking specifically at nsEmbedAPI.cpp. It is loading the
`necko.properties' file.

> pete, the bundle has to be loaded SOMETIME, so really it's just a matter of
> figuring out if we can offload it onto a seperate thread or not.

Agree, but the initial load that registers all of the chrome takes about 10
seconds. That is completely unacceptable. I'm not even addressing *that* issue.

;-)

I'm just stepping through things and this was the first performance hit i
encountered so i figured i'd investigate.

If we can shave off a second here a second there and a few milliseconds
somewhere else, that will begin to add up to greatly reducing mozilla's startup
time.

I think i understand now that by going through nsIInputStream we put everything
on hold until the thread can move forward. This wasn't clear to me before.

--pete

it's still async, but you can use a stream loader 
http://lxr.mozilla.org/mozilla/source/netwerk/base/public/nsIStreamLoader.idl if 
 all you want is a single callback. you'll still need to break the caller apart 
from data retrieval (and fire up an event loop), it's just easier to handle the 
single callback using the stream loader.

I agree w/ alec that wedging this model in at the string bundle caller level 
would be quite hard. I reopened this bug because it is obviously showing up on 
perf radar somewhere. if we want to resolve it WONTFIX (or future), that's fine, 
but we need to be clear that we're paying a heavy penalty using 
OpenInputStream() here.
You might want to look at some findings i came accross here.
It is related to some extent.

http://bugzilla.mozilla.org/show_bug.cgi?id=76865

--pete
the current code is very broken.. I've just discovered that this whole callback
mechanism doesn't work at all.. why? Because if you are using the "async" stuff
(which is not really async) then what happens is this:

var bundle = stringBundleService.CreateBundle("chrome://foo/bar.properties") 
// creates the bundle, using an event loop.. returns when the bundle has
finished loading

bundle.RegisterCallback(foo, closure);
// callback is never called, because the bundle has already been loaded!

so I'm attaching patches which do the following:
- in CreateBundle(), all we do is store the URL of the bundle that the user
asked for
- adding LoadProperties() which loads the property using the event loop
mechanism
- on the entry point to GetStringFromName(), GetStringFromID(), and
FormatStringFromName(), I'm calling LoadProperties, which will load the bundle
if necessary
- removes the defunct RegisterCallback mechanism

what this essentially accomplishes is the ability to arbitrarily create bundles
very quickly, but have them loaded as soon as you ask for the first string. I'm
also keeping track of whether the bundle was ever loaded, so that if you keep
trying to access the same string over and over, it doesn't keep trying to load
the bundle.

this improves startup performance because on startup we create 8 bundles, but
only ask for strings from 7 of them.. so one of them won't even be loaded until
it's necessary.

This does NOT fix the actual issue of async bundle loading, but it at least
makes them lazy. 

Once I attach the patch, I'll be looking for reviewers!

Status: NEW → ASSIGNED
at valeski's request, spun off into bug 76944
Blocks: 7251
Depends on: 76944
Summary: string bundle shouldn't be using using OpenInputStream() → string bundle shouldn't be using using OpenInputStream(), block UI thread
nav triage team:

We want this code, but it's not a beta stopper. Marking nsbeta1-
Keywords: nsbeta1-
Nav triage team:  not needed for 0.9.4 eMojo, leaving in 1.0.
No longer blocks: 7251
Blocks: 7251
discussed in perf triage meeting today.  agreed to put this one off... for later.
performance triage meeting, changing milestone 0.9.6
Target Milestone: mozilla1.0 → mozilla0.9.6
Can some look at bug 76682 and see if the bugs describe the same problem? TIA.
moving out to 0.9.7, I never said I had time to do this, and it's not even clear
there is any way to do this in a way that would improve performance.
Target Milestone: mozilla0.9.6 → mozilla0.9.7
this just isn't buying us any performance win right now. pushing out to 0.9.8
Target Milestone: mozilla0.9.7 → mozilla0.9.8
pushing out non-critical bugs past moz 1.0
Target Milestone: mozilla0.9.8 → mozilla1.1
if this isn't giving any performance improvements why does it block bug 7251 ?
Blocks: 133698
Target Milestone: mozilla1.1alpha → mozilla1.2alpha
it doesn't now.
No longer blocks: 7251
Target Milestone: mozilla1.2alpha → Future
QA Contact: tao → i18n
Moving to p3 because no activity for at least 1 year(s).
See https://github.com/mozilla/bug-handling/blob/master/policy/triage-bugzilla.md#how-do-you-triage for more information
Priority: P2 → P3

We're not going to invest in StringBundle anymore.

Status: ASSIGNED → RESOLVED
Closed: 24 years ago3 years ago
Resolution: --- → INACTIVE
You need to log in before you can comment on or make changes to this bug.