Closed Bug 1778888 Opened 2 years ago Closed 2 years ago

nsIMsgFolder.getStringProperty throws errors too often

Categories

(Thunderbird :: General, defect)

Thunderbird 102
defect

Tracking

(thunderbird_esr102+ fixed, thunderbird103 wontfix)

RESOLVED FIXED
104 Branch
Tracking Status
thunderbird_esr102 + fixed
thunderbird103 --- wontfix

People

(Reporter: axel.grude, Assigned: benc)

References

(Blocks 1 open bug)

Details

Attachments

(1 file, 2 obsolete files)

This has serious performance implications on my Add-on QuickFolders.

When building a MRU list if call folder.getStringProperty("MRUTime") - this throws if the property doesn't exist. It now takes about 8 Seconds to build the mru list (it was 200ms in Thunderbird 91)
The error thrown is:

Component returned failure code: 0x80550007 [nsIMsgFolder.getStringProperty]

When trying to look for icon modifications in the folder tree I use folder.getStringProperty("folderIcon") which throws if the attribute is not added, again effecting serious performance penalties. The whole UI of Thundebird gets extremely sluggish, the more folders are shown in the tree.

Would it be possible to patch getStringProperty so that it doesn't throw and just returns NULL when the property doesn't exist? There is no other way to find out about a nsIMsgFolder property

Flags: needinfo?(geoff)
Flags: needinfo?(benc)

Not my territory, likely something to do with bug 418551. I don't see how throwing would be that much slower than returning a value.

Flags: needinfo?(geoff)
Flags: needinfo?(mkmelin+mozilla)

2022-07-11_1317_mass-mark-TB102found

Blocks: tb102found

Hmm. I could definitely imagine exceptions causing a slowdown. And even if we didn't, we definitely shouldn't be relying on exceptions in normal operation, so it sounds to me like a better solution is required.

I don't think we can easily return null for undefined properties - the idl spec specifies a string and I don't think there's any way to return null via C++ (it appears as a nsACString& param in the C++ signature).
We could return an empty string, but that wouldn't let us distinguish between "" and an undefined property.

Axel: off the top of your head, do you know what the old behaviour was? (I had a quick peek at the bug 418551 changes, but it's not obvious)

I think a proper isPropertyDefined() kind of method would be the way forward here...

Flags: needinfo?(benc)

I wouldn't expect throwing to cause that much of an overhead. Perhaps the problem is a symptom of something else, like problem with the .msf files (not existing perhaps)?

Like Ben I'd also like to know what the old behavior was. Did it really change?

Flags: needinfo?(mkmelin+mozilla)

The behaviour has changed, null values in the folder cache are no longer returned and cause an error. This caused bug 1726319, bug 1773822, bug 1773401 and bug 1776280. Please refer to bug 1776280 comment #7 and cmt. #9.

Attached patch 1778888-folder-cache-null-value.patch (obsolete) (deleted) — Splinter Review

We've set all MRUTime entries to null in folderCache.json and then ran "Quick Folder Move". Same speed as without the patch and all MRUTimes populated after bug 1776280. So this is the better solution.

Attachment #9285187 - Flags: review?(benc)

(In reply to b1 from comment #6)

Created attachment 9285187 [details] [diff] [review]
1778888-folder-cache-null-value.patch

We've set all MRUTime entries to null in folderCache.json and then ran "Quick Folder Move". Same speed as without the patch and all MRUTimes populated after bug 1776280. So this is the better solution.

not bad - I was thinking along the same line for my custom property "folderIcon" - instead of setting it for only the customized folders I could set it to an empty string to all folders in the tree at startup, this would also avoid the throwing. Will do some testing along these lines this week. I call the getStringProperty function from the gFolderTreeView.getCellProperties() in order to generate a CSS stylable attribute on the element in folderNameCol, and there are a lot of calls for that one.

(In reply to Ben Campbell from comment #3)

Axel: off the top of your head, do you know what the old behaviour was? (I had a quick peek at the bug 418551 changes, but it's not obvious)

I think a proper isPropertyDefined() kind of method would be the way forward here...

certainly an isPropertyDefined() or hasStringProperty() function would be a godsend. Would it be hard to implement?

Comment on attachment 9285187 [details] [diff] [review] 1778888-folder-cache-null-value.patch Review of attachment 9285187 [details] [diff] [review]: ----------------------------------------------------------------- This patch makes `nsIMsgFolder.getStringProperty()` return null/undefined properties as empty strings (""), which I guess is the old behaviour. I _was_ thinking that adding a `nsIMsgFolder.hasStringPropery()` was a cleaner way to go here, but as I was reviewing the code I came around to thinking that using an empty string to signify a missing/unset string property probably isn't such a bad thing after all. The only issue is that there would be no way currently to distinguish between an empty string and an unset value. But that's not been a problem so far (and we can always still add a `nsIMsgFolder.hasStringProperty()` if the distinction really is important). Axel, any opinions on this? So I'm happy with the intent of the patch, although I'd prefer it was implemented a bit differently: 1. I think it should all be handled in `nsMsgDBFolder.GetStringProperty()`, rather than down in `nsMsgFolderCacheElement::GetCachedString()`. There are equivalent numeric methods in `nsMsgFolderCacheElement` and it seems confusing that they should behave differently to the string method. (the integer methods are not exposed publicly - there are no numeric equivalents to `nsMsgDBFolder.GetStringProperty()`). 2. The `getStringProperty()` signature in nsIMsgFolder.idl needs to be documented accordingly. 3. To be consistent, the corresponding `setStringProperty()` should probably remove/unset the property if the string is empty? 4. It'd be good to add some unit tests to sanitycheck and illustrate the behaviour (I can't see any unit test coverage of `nsMsgDBFolder.GetStringProperty()` at all). That's a lot more than formatting changes, so I'm happy to rework a patch if you'd like (although I probably won't get a chance to do it this week). ::: mailnews/base/src/nsMsgFolderCache.cpp @@ +54,1 @@ > _retval = o.asString().c_str(); This is the bit - it'll return an empty string "" for null or undefined/missing entries ([source code](https://searchfox.org/mozilla-central/source/toolkit/components/jsoncpp/src/lib_json/json_value.cpp#631)).
Attachment #9285187 - Flags: review?(benc) → review-

Thanks for the review. This was the easiest way to return to TB 91 behaviour after all the regressions the behaviour change has caused, see comment #5. We'll ship this in our fork until a better solution arrives. Sadly there are more regressions and people haven't made the connection yet: If MSFs are deleted (bug 1773822) of course virtual/unified folders stop working (maybe related to bug 1777606) and filters also stop working (bug 1777731).

Note that no uplift was requested in bug 1776280, so users of "recent anything" are still suffering slowness, see bug 1773401.

We believe there is a difference between a property missing the the folder cache and a property being present with a value of null. In all the data we've seen, MRUTime and LastPurgeTime was null, not missing, so being null should not return an error, whereas missing should go off and fetch the value from the database. Side note: We don't quite understand the inner workings of Mork, looking at some panacea.dat files we got the impression that those two timestamp properties were "mentioned" in the "header" without specific individual values. Maybe that caused a general "default" null value to be returned which is now visible in folderCache.json after migration.

Your point three: Were not sure that remove and unset have the same semantic, remove leads to missing from the cache (causing a DB access) and unset causes a null value.

Just repeating: The behaviour change caused exisiting null values to be rejected and then databases were opened (and not closed again, bug 1773822) to fetch the value. That caused hitting the "max open files" and subsequent MSF deletion since any error in opening the MSF deemed it invalid and caused its deletion. And even after successful retrieval the value wasn't added to the cache (bug 1776280). It took quite some time to a) recognise the behaviour change of the folder cache and b) understand all the series of events and consequences.

Or are you trying to distinguish between null and empty string? They are the same, in all the conversions from char* to nsCString in the past, a null char pointer or a char pointer pointing to a single null byte were replaced with EmptyCString(). What we think needs to be distinguished is: value present in the cache, be it null, empty string on non-empty string, and value missing from the cache (needs to trigger retrieval from the DB).

That's why believe that replacing if (!o.isNull() && o.isConvertibleTo(Json::stringValue)) with if (o.isConvertibleTo(Json::stringValue)) is the correct change. The quoted M-C code will return an empty string for a null value which is desirable. If you're at that point in the code, there was an entry in the cache, so we don't understand what you mean by "undefined/missing entries". Undefined is not a good term in this context, the value is defined, it's null, and it's also not missing.

Looks like the panacea.dat to JSON migration was wrong then and should have migrated default/empty/null values to an empty string instead of null since null can't be distinguished from missing :-( - Nothing in folderCache.json should be null since that's considered "not present".

(In reply to b1 from comment #12)

Well, looks like the JSON lookup returns null even for missing entries:
https://searchfox.org/mozilla-central/rev/0d11f3660945ce35c49501bb44bc4f82bb2b503c/toolkit/components/jsoncpp/src/lib_json/json_value.cpp#1104,1110

Yes, sorry, I wasn't explicit enough about that - jsoncpp seems to use null to represent things that JS would use undefined for...
I've been picking my way through the jsoncpp library, and there is an isMember() method, which would let us check if a entry exists before fetching it. So we can make the distinction.
The question is how to represent that in our .getStringProperty()?

What you wrote in comment #10 seems to point the way:

(In reply to b1 from comment #10)

We believe there is a difference between a property missing the the folder cache and a property being present with a value of null. In all the data we've seen, MRUTime and LastPurgeTime was null, not missing, so being null should not return an error, whereas missing should go off and fetch the value from the database.

SO... how about:

  1. in the cache code,
  • null (actually null in json!) cache value returns empty string.
  • entirely missing throws exception (rv=NS_ERR_NOT_FOUND or whatever it is)
  • apply this to the numeric methods too, to be consistent (so actual null reads as 0, which seems reasonable).
  1. in nsIMsgFolder:
  • add a .hasStringProperty() and maybe a .removeStringProperty() to nsiMsgFolder.

I think that behaviour would sort this all out nicely, right?

Attached patch 1778888-folder-cache-null-value.patch (obsolete) (deleted) — Splinter Review

Thanks for the tip. This should be the right solution then. Feel free to refactor it and apply the same principle to numeric retrieval. Some tests to document the behaviour would of course also be very useful. Please attribute the research here to our project.

Attachment #9285187 - Attachment is obsolete: true
Comment on attachment 9285450 [details] [diff] [review] 1778888-folder-cache-null-value.patch Review of attachment 9285450 [details] [diff] [review]: ----------------------------------------------------------------- Actually, I think this patch is probably a good one to land as-is. It fixes the immediate problem, doesn't invalidate any behaviour described in IDL comments and is small enough to for uplift. I say land this one and I'll do a followup to do the same thing for the numeric accessors, add hasProperty/removeProperty methods, document nsMsgFolder.getStringProperty(), and add some test coverage.
Attachment #9285450 - Flags: review+
Assignee: nobody → benc
Keywords: leave-open

Thanks, we can't set "checkin needed" since we didn't file the bug.

And maybe it would be best to do more work in a new bug to avoid the situation where only a part is uplifted.

Blocks: 1779663

Oop - no, hang off on landing this - comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js fails (getCachedString() doesn't throw an exception for non-exsitent key). Investigating now.

Attachment #9285450 - Flags: review+ → review-

Easy fix. Turns out the JsonCpp Value& Value::operator[char*] method creates a null entry in the object if it doesn't already exist. So we just need to do the isMember() check first.
The numeric methods have to do that too, because I used the same non-existent-key name in test_nsIMsgFolderCache.js :-)

try run in progress:
https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=a59cfc6873258a49e9f0a3535248e14fc7c9aae6

Attachment #9285450 - Attachment is obsolete: true
Attachment #9285581 - Flags: review+

Could someone try this patch to see if it fixes the QuickFolders issue?
If so, and the try run looks OK, then we land it and flag it for uplift...

(In reply to Ben Campbell from comment #20)

Easy fix. Turns out the JsonCpp Value& Value::operator[char*] method creates a null entry in the object if it doesn't already exist.
So we just need to do the isMember() check first.

Thanks for the adjustment and sorry for not running the test. We wanted to avoid two lookups of the same key, hence the isMember() was only called if a null value was returned.

This can land now.

Flags: needinfo?(mkmelin+mozilla)
Flags: needinfo?(rob)

I've marked it to get landed now.

Status: NEW → ASSIGNED
Flags: needinfo?(rob)
Flags: needinfo?(mkmelin+mozilla)
Target Milestone: --- → 104 Branch

Pushed by mkmelin@iki.fi:
https://hg.mozilla.org/comm-central/rev/2f49d0b8575f
Revert bug 1726319 and bug 1776280 and allow folder cache to return empty strings for null values. r=benc

Status: ASSIGNED → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED

uplift to 102 with bug 1776280?

Flags: needinfo?(benc)

(In reply to Wayne Mery (:wsmwk) from comment #27)

uplift to 102 with bug 1776280?

Yes, I think so.
The patch in bug 1776280 isn't needed - this patch rolls it back out. But maybe the one in bug 1776280 needs to be taken first so that this one applies cleanly?

Flags: needinfo?(benc)

Comment on attachment 9285581 [details] [diff] [review]
1778888-folder-cache-null-value-2.patch

[Approval Request Comment]
Regression caused by (bug #):
User impact if declined:
Testing completed (on c-c, etc.):
Risk to taking this patch (and alternatives if risky):

Attachment #9285581 - Flags: approval-comm-esr102?

Comment on attachment 9285581 [details] [diff] [review]
1778888-folder-cache-null-value-2.patch

[Triage Comment]
Approved for esr102

Take this after bug 1776280

Flags: needinfo?(rob)
Attachment #9285581 - Flags: approval-comm-esr102? → approval-comm-esr102+
Flags: needinfo?(rob)

Thanks for fixing, I can confirm displaying my recent folders is orders of magnitude faster. I would supply quantitative data on this but logging seems to be borked:

See bug 1785222

Regressions: 1788901
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: