WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
83526
[V8] IndexedDB: Cursor value modifications should be preserved until cursor iterates
https://bugs.webkit.org/show_bug.cgi?id=83526
Summary
[V8] IndexedDB: Cursor value modifications should be preserved until cursor i...
Joshua Bell
Reported
2012-04-09 16:48:58 PDT
The IndexedDB spec
http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html
sayeth, re: IDBCursorWithValue "Returns the cursor's current value. Note that if this property returns an object, it returns the same object instance every time it is inspected, until the cursor's value is changed. This means that if the object is modified, those modifications will be seen by anyone inspecting the value of the cursor. However modifying such an object does not modify the contents of the database." The WebKit implementation does not currently support the "same object instance every time" semantics, meaning the following test would fail: // assume cursor.value = {a: 1, b: 2} cursor.value.a = 3; delete cursor.value.b; cursor.value.foo = "bar"; assert(cursor.value.a === 3); assert(cursor.value.b === undefined); assert(cursor.value.foo === "bar"); It may be possible to do this via [CachedAttribute] or, since IDBAny already has a custom V8 binding, with custom caching code. Or possible via an IDL attribute on IDBCursorWithValue that ensure the same IDBAny is returned each time?
Attachments
Layout test for spec behavior
(3.70 KB, patch)
2012-04-09 16:51 PDT
,
Joshua Bell
no flags
Details
Formatted Diff
Diff
Patch
(13.15 KB, patch)
2012-06-11 13:58 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Patch
(13.05 KB, patch)
2012-06-11 15:40 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Patch
(14.39 KB, patch)
2012-06-11 15:52 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Patch
(14.39 KB, patch)
2012-06-11 15:52 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Patch
(16.27 KB, patch)
2012-06-11 17:23 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Patch
(19.53 KB, patch)
2012-06-12 10:54 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Patch for landing
(19.11 KB, patch)
2012-06-13 10:01 PDT
,
Alec Flett
no flags
Details
Formatted Diff
Diff
Show Obsolete
(7)
View All
Add attachment
proposed patch, testcase, etc.
Joshua Bell
Comment 1
2012-04-09 16:51:37 PDT
Created
attachment 136345
[details]
Layout test for spec behavior
Joshua Bell
Comment 2
2012-06-06 13:02:13 PDT
This is pesky. I thought it might be as simple as changing IDBCursorWithValue.idl - readonly attribute IDBAny value; + readonly attribute [CachedAttribute] SerializedScriptValue value; (plus appropriate .h/.cpp updates) but this is insufficient since the same IDBCursor object is returned across continue() calls and so the cached value gets re-used incorrectly. FWIW, if the above is done the autogenerated code (for V8) looks like: static v8::Handle<v8::Value> valueAttrGetter(v8::Local<v8::String> name, const v8::AccessorInfo& info) { INC_STATS("DOM.IDBCursorWithValue.value._get"); v8::Handle<v8::String> propertyName = v8::String::NewSymbol("value"); v8::Handle<v8::Value> value = info.Holder()->GetHiddenValue(propertyName); if (!value.IsEmpty()) return value; IDBCursorWithValue* imp = V8IDBCursorWithValue::toNative(info.Holder()); SerializedScriptValue* serialized = imp->value(); value = serialized ? serialized->deserialize() : v8::Handle<v8::Value>(v8::Null(info.GetIsolate())); info.Holder()->SetHiddenValue(propertyName, value); return value; } There's no check that the SSV hasn't changed, nor a way to invalidate this cache outside the binding layer. Bleah. The V8IDBAnyCustom.cpp implementation of toV8 unfortunately doesn't get passed the AccessorInfo object so there's no place for it to stash a value. We may need to do custom binding for IDBCursorWithValue
David Grogan
Comment 3
2012-06-07 14:46:21 PDT
(In reply to
comment #0
)
> "Returns the cursor's current value. Note that if this property returns an object, it returns the same object instance every time it is inspected, until the cursor's value is changed. This means that if the object is modified, those modifications will be seen by anyone inspecting the value of the cursor. However modifying such an object does not modify the contents of the database." > > The WebKit implementation does not currently support the "same object instance every time" semantics
I thought Hans added this for prefetching and that cursor-inconsistency.html tested it?
Joshua Bell
Comment 4
2012-06-07 15:30:33 PDT
(In reply to
comment #3
)
> (In reply to
comment #0
) > > "Returns the cursor's current value. Note that if this property returns an object, it returns the same object instance every time it is inspected, until the cursor's value is changed. This means that if the object is modified, those modifications will be seen by anyone inspecting the value of the cursor. However modifying such an object does not modify the contents of the database." > > > > The WebKit implementation does not currently support the "same object instance every time" semantics > > I thought Hans added this for prefetching and that cursor-inconsistency.html tested it?
Similar but subtly different: the same IDBCursor is indeed returned each time (i.e. request.result), but the cursor's value (e.g. request.result.value) is deserialized fresh from the SSV on each property access.
Alec Flett
Comment 5
2012-06-11 12:14:20 PDT
I think I have a fix, I'll use the test here.
Alec Flett
Comment 6
2012-06-11 13:58:14 PDT
Created
attachment 146900
[details]
Patch
Alec Flett
Comment 7
2012-06-11 14:21:04 PDT
jsbell@ / dgrogan@ - quick review?
Joshua Bell
Comment 8
2012-06-11 14:55:16 PDT
Comment on
attachment 146900
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=146900&action=review
> Source/WebCore/ChangeLog:8 > + Cache the 'value' attribute of IDBCursorWithValue with policy
Should mention in the ChangeLog that this is explicit in the spec.
> Source/WebCore/Modules/indexeddb/IDBCursor.h:86 > + bool valueIsDirty() { return m_valueIsDirty; }
I think this method (or the field) merits a comment, since it's not obvious why this would exist and subtle in the spec.
> Source/WebCore/Modules/indexeddb/IDBCursorWithValue.idl:31 > + readonly attribute [V8CustomGetter] IDBAny value;
Would it simplify the code to change IDBAny here to SerializedScriptValue?
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:28 > + LOG_ERROR(".value => is dirty? %d", imp->valueIsDirty());
Remove LOG_ERROR calls here and below.
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:37 > + // either value is dirty, or the hidden value is empty
Can remove this comment, it's clear from the code.
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:39 > + v8::Handle<v8::Value> wrapper = result.get() ? getDOMObjectMap(info.GetIsolate()).get(result.get()) : v8::Handle<v8::Object>();
Definitely get a V8/bindings expert to review this bit.
Alec Flett
Comment 9
2012-06-11 15:40:10 PDT
Created
attachment 146933
[details]
Patch
Joshua Bell
Comment 10
2012-06-11 15:49:59 PDT
Comment on
attachment 146933
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=146933&action=review
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:1 > +
Copyright header? (Some but not all of the files in bindings/v8 appear to have them, I note.)
Alec Flett
Comment 11
2012-06-11 15:51:36 PDT
* I don't think it simplifies much to use SerializedScriptValue - just changes the transformation slightly, possibly making IDBCursor.cpp more complex in the process. haraken@ - I wrote this custom binding as a sort of merge of the standard binding code and the CachedAttribute code. I think I likely have one extra layer of caching in here but I don't understand this completely.. can you take a look at IDBCustomBindings.cpp in this patch? Do I need all the goop calling getDOMObjectMap() and such? (And the final patch will need to update the other build systems too, I will get to that before webkit-review)
Alec Flett
Comment 12
2012-06-11 15:52:03 PDT
Created
attachment 146941
[details]
Patch
Alec Flett
Comment 13
2012-06-11 15:52:21 PDT
Created
attachment 146942
[details]
Patch
Kentaro Hara
Comment 14
2012-06-11 17:03:56 PDT
Comment on
attachment 146942
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=146942&action=review
At a high level question: Does the cache really improve performance? We need to care that GetHiddenValue() and SetHiddenValue() are not so light. In case of SSV, since the serialization overhead is large, the cache management cost will pay. On the other hand, in case of 'value', I am not sure if the cache management cost pays or not. Did you confirm any performance gain in some benchmark (artificial one is OK)?
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:14 > + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
Please use the correct copyright of Google.
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:41 > +#include "ContextEnabledFeatures.h" > +#include "RuntimeEnabledFeatures.h" > +#include "V8Binding.h" > +#include "V8BindingState.h" > +#include "V8DOMWrapper.h" > +#include "V8HiddenPropertyName.h" > +#include "V8IDBAny.h" > +#include "V8IDBCursor.h" > +#include "V8IsolatedContext.h" > +#include "V8Proxy.h" > +#include <wtf/UnusedParam.h>
It seems that unused headers are included. e.g. "ContextEnabledFeatures.h", "RuntimeEnabledFeatures.h", etc. Please check each of them if it is needed or not.
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:46 > + const v8::AccessorInfo& info)
Nit: you do not need to wrap the code.
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:65 > + RefPtr<IDBAny> result = imp->value(); > + v8::Handle<v8::Value> wrapper = result.get() ? getDOMObjectMap(info.GetIsolate()).get(result.get()) : v8::Handle<v8::Object>(); > + if (wrapper.IsEmpty()) { > + wrapper = toV8(result.get(), info.GetIsolate()); > + if (!wrapper.IsEmpty()) > + V8DOMWrapper::setNamedHiddenReference(info.Holder(), "value", wrapper); > + } > + info.Holder()->SetHiddenValue(propertyName, wrapper);
getDOMObjectMap() and setNamedHiddenReference() are used for keeping the lifetime of 'wrapper' over GC, which is sometimes required by semantics. But in this case, you just want to cache the value for performance (i.e. cache miss won't violate semantics), and you do not need to care the lifetime management. So I think that this code can be: RefPtr<IDBAny> result = imp->value(); v8::Handle<v8::Value> wrapper = toV8(result.get(), info.GetIsolate()); info.Holder()->SetHiddenValue(propertyName, wrapper); return wrapper;
Alec Flett
Comment 15
2012-06-11 17:23:00 PDT
Created
attachment 146974
[details]
Patch
Alec Flett
Comment 16
2012-06-11 17:24:05 PDT
Comment on
attachment 146974
[details]
Patch kentaro@ - thanks - this isn't for performance, this is for spec compliance - like you I suspect this will have a negligible effect on performance. We actually need the JS object to survive mutations during the lifetime of the attribute value. specifically imagine code like this: cursor.value.foo = "bar"; ... if (cursor.value.foo == "bar") { ....} cursor.advance() // changes the value of cursor.value here if (cursor.value.foo == "bar") { ... } New patch is up. r? cq?
Alec Flett
Comment 17
2012-06-11 17:24:42 PDT
[I just realized I still need to do XCode project files.. ]
Kentaro Hara
Comment 18
2012-06-11 17:48:41 PDT
Comment on
attachment 146974
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=146974&action=review
> this isn't for performance, this is for spec compliance
I am sorry! I should have read your ChangeLog carefully... For now let me mark r- due to missing necessary tests to confirm the change.
> Source/WebCore/ChangeLog:9 > + determined by IDBCursor.cpp, to follow spec behavior of keeping a
Link to the spec please.
> Source/WebCore/ChangeLog:12 > + Test: storage/indexeddb/cursor-value.html
Would you add the following test cases? (Case 1) cursor.value.foo = "bar"; shouldBe('cursor.value.foo', ...); cursor.advance(); shouldBe('cursor.value.foo', ...); (Case 2) cursor.value.foo = "bar"; shouldBe('cursor.value.foo', ...); gc(); shouldBe('cursor.value.foo', ...);
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:2 > + * Copyright (C) 2011, Google Inc. All rights reserved.
2011 => 2012
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:13 > + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
Please do not change the line manually. Please just copy the whole copyright from bindings/v8/custom/V8TrackEventCustom.cpp, and just change "2011" to "2012".
> Source/WebCore/bindings/v8/IDBCustomBindings.cpp:50 > + IDBCursorWithValue* imp = V8IDBCursorWithValue::toNative(info.Holder()); > + v8::Handle<v8::String> propertyName = v8::String::NewSymbol("value"); > + if (!imp->valueIsDirty()) { > + v8::Handle<v8::Value> value = info.Holder()->GetHiddenValue(propertyName); > + if (!value.IsEmpty()) > + return value; > + } > + > + RefPtr<IDBAny> result = imp->value(); > + v8::Handle<v8::Value> wrapper = toV8(result.get(), info.GetIsolate()); > + info.Holder()->SetHiddenValue(propertyName, wrapper); > + return wrapper;
Confirmation: The semantics you need is: - If the value is not yet invalidated, return the same wrapper object keeping JavaScript properties. - If the value is already invalidated, create a new wrapper object. Is my understanding correct? If so, the code looks correct.
Alec Flett
Comment 19
2012-06-12 10:54:31 PDT
Created
attachment 147113
[details]
Patch
Alec Flett
Comment 20
2012-06-12 10:56:35 PDT
Comment on
attachment 147113
[details]
Patch kentaro@ - all issues addressed - cursor advancing/continuing is async, so the tests can be awkward, but I handled both cases, and rolled gc'ing into each case (to be fair I copied that copyright notice in whole from another file, sounds like I should fix that separately) in any case, r? cq?
Kentaro Hara
Comment 21
2012-06-12 16:49:48 PDT
Comment on
attachment 147113
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=147113&action=review
Looks OK except for nits.
> Source/WebCore/WebCore.gypi:1884 > + 'bindings/v8/IDBCustomBindings.cpp',
Is this correct? IDBCustomBindings.cpp is added twice.
> LayoutTests/storage/indexeddb/resources/cursor-value.js:56 > + index += 2; > + } else if (index == 3) {
Nit: why are you using index+=2 instead of index++?
Alec Flett
Comment 22
2012-06-13 10:01:35 PDT
Created
attachment 147350
[details]
Patch for landing
Alec Flett
Comment 23
2012-06-13 10:24:16 PDT
jsbell / dgrogan - cq?
Alec Flett
Comment 24
2012-06-13 10:31:50 PDT
haraken: the += 2 was just to keep the index in sync with the actual index of the items we're iterating, since we're calling advance(2) - it's in sync mainly for test readability/maintenance
WebKit Review Bot
Comment 25
2012-06-13 13:30:08 PDT
Comment on
attachment 147350
[details]
Patch for landing Clearing flags on attachment: 147350 Committed
r120241
: <
http://trac.webkit.org/changeset/120241
>
WebKit Review Bot
Comment 26
2012-06-13 13:30:16 PDT
All reviewed patches have been landed. Closing bug.
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug