⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 242585 in webkit


Ignore:
Timestamp:
Mar 6, 2019, 6:03:39 PM (7 years ago)
Author:
sbarati@apple.com
Message:

JSScript should keep the cache file locked for the duration of its existence and should truncate the cache when it is out of date
https://bugs.webkit.org/show_bug.cgi?id=195186

Reviewed by Keith Miller.

This patch makes it so that JSScript will keep its bytecode cache file
locked as long as the JSScript is alive. This makes it obvious that it's
safe to update that file, as it will only be used in a single VM, across
all processes, at a single time. We may be able to extend this in the future
if we can atomically update it across VMs/processes. However, we're choosing
more restricted semantics now as it's always easier to extend these semantics
in the future opposed to having to support the more flexible behavior
up front.

This patch also:

  • Adds error messages if writing the cache fails. We don't expect this to fail, but previously we would say we cached it even if write() fails.
  • Removes the unused m_moduleKey field.
  • Makes calling cacheBytecodeWithError with an already non-empty cache file fail. In the future, we should extend this to just fill in the parts of the cache that are not present. But we don't have the ability to do that yet, so we just result in an error for now.
  • API/JSScript.mm:

(-[JSScript dealloc]):
(-[JSScript readCache]):
(-[JSScript init]):
(-[JSScript writeCache:]):

  • API/JSScriptInternal.h:
  • API/tests/testapi.mm:

(testCacheFileIsExclusive):
(testCacheFileFailsWhenItsAlreadyCached):
(testObjectiveCAPI):

Location:
trunk/Source/JavaScriptCore
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/API/JSScript.mm

    r242239 r242585  
    5252    JSC::CachedBytecode m_cachedBytecode;
    5353    JSC::Strong<JSC::JSSourceCode> m_jsSourceCode;
    54     UniquedStringImpl* m_moduleKey;
     54    int m_cacheFileDescriptor;
    5555}
    5656
     
    175175    if (m_cachedBytecode.size() && !m_cachedBytecode.owned())
    176176        munmap(const_cast<void*>(m_cachedBytecode.data()), m_cachedBytecode.size());
     177
     178    if (m_cacheFileDescriptor != -1)
     179        close(m_cacheFileDescriptor);
     180
    177181    [super dealloc];
    178182}
     
    183187        return;
    184188
    185     int fd = open([m_cachePath path].UTF8String, O_RDONLY);
    186     if (fd == -1)
     189    m_cacheFileDescriptor = open([m_cachePath path].UTF8String, O_CREAT | O_RDWR | O_EXLOCK | O_NONBLOCK, 0666);
     190    if (m_cacheFileDescriptor == -1)
    187191        return;
    188192
    189     int rc = flock(fd, LOCK_SH | LOCK_NB);
    190     if (rc) {
    191         close(fd);
     193    struct stat sb;
     194    int res = fstat(m_cacheFileDescriptor, &sb);
     195    size_t size = static_cast<size_t>(sb.st_size);
     196    if (res || !size)
    192197        return;
    193     }
    194 
    195     struct stat sb;
    196     int res = fstat(fd, &sb);
    197     size_t size = static_cast<size_t>(sb.st_size);
    198     if (res || !size) {
    199         close(fd);
    200         return;
    201     }
    202 
    203     void* buffer = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
    204     close(fd);
     198
     199    void* buffer = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, m_cacheFileDescriptor, 0);
    205200
    206201    JSC::CachedBytecode cachedBytecode { buffer, size };
     
    211206    if (isCachedBytecodeStillValid(vm, cachedBytecode, key, m_type == kJSScriptTypeProgram ? JSC::SourceCodeType::ProgramType : JSC::SourceCodeType::ModuleType))
    212207        m_cachedBytecode = WTFMove(cachedBytecode);
     208    else
     209        ftruncate(m_cacheFileDescriptor, 0);
    213210}
    214211
     
    239236@implementation JSScript(Internal)
    240237
     238- (instancetype)init
     239{
     240    self = [super init];
     241    if (!self)
     242        return nil;
     243
     244    m_cacheFileDescriptor = -1;
     245    return self;
     246}
     247
    241248- (unsigned)hash
    242249{
     
    264271- (BOOL)writeCache:(String&)error
    265272{
    266     if (m_cachedBytecode.size())
    267         return YES;
    268 
    269     if (!m_cachePath) {
    270         error = "No cache was path provided during construction of this JSScript."_s;
     273    if (m_cachedBytecode.size()) {
     274        error = "Cache for JSScript is already non-empty. Can not override it."_s;
     275        return NO;
     276    }
     277
     278    if (m_cacheFileDescriptor == -1) {
     279        if (!m_cachePath)
     280            error = "No cache was path provided during construction of this JSScript."_s;
     281        else
     282            error = "Could not lock the bytecode cache file. It's likely another VM or process is already using it."_s;
    271283        return NO;
    272284    }
     
    288300    }
    289301
    290     int fd = open([m_cachePath path].UTF8String, O_CREAT | O_WRONLY, 0666);
    291     if (fd == -1) {
    292         error = makeString("Unable to open file: ", [m_cachePath path].UTF8String, " due to error: ", strerror(errno));
    293         return NO;
    294     }
    295     int returnCode = flock(fd, LOCK_EX | LOCK_NB);
    296     if (returnCode)
    297         error = "Unable to lock the cache file; it may already be in use."_s;
    298     else
    299         write(fd, m_cachedBytecode.data(), m_cachedBytecode.size());
    300     close(fd);
    301     return !returnCode;
     302    ssize_t bytesWritten = write(m_cacheFileDescriptor, m_cachedBytecode.data(), m_cachedBytecode.size());
     303    if (bytesWritten == -1) {
     304        error = makeString("Could not write cache file to disk: ", strerror(errno));
     305        return NO;
     306    }
     307
     308    if (static_cast<size_t>(bytesWritten) != m_cachedBytecode.size()) {
     309        ftruncate(m_cacheFileDescriptor, 0);
     310        error = makeString("Could not write the full cache file to disk. Only wrote ", String::number(bytesWritten), " of the expected ", String::number(m_cachedBytecode.size()), " bytes.");
     311        return NO;
     312    }
     313
     314    return YES;
    302315}
    303316
  • trunk/Source/JavaScriptCore/API/JSScriptInternal.h

    r241929 r242585  
    4545@interface JSScript(Internal)
    4646
     47- (instancetype)init;
    4748- (unsigned)hash;
    4849- (const WTF::String&)source;
  • trunk/Source/JavaScriptCore/API/tests/testapi.mm

    r242239 r242585  
    21602160}
    21612161
     2162static void testCacheFileIsExclusive()
     2163{
     2164    NSURL* cachePath = tempFile(@"foo.program.cache");
     2165
     2166    @autoreleasepool {
     2167        NSString *source = @"function foo() { return 42; } foo();";
     2168        NSURL* sourceURL = [NSURL URLWithString:@"my-path"];
     2169        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
     2170
     2171        JSScript *script1 = [JSScript scriptOfType:kJSScriptTypeProgram withSource:source andSourceURL:sourceURL andBytecodeCache:cachePath inVirtualMachine:vm error:nil];
     2172        RELEASE_ASSERT(script1);
     2173        checkResult(@"Should be able to cache the first file", [script1 cacheBytecodeWithError:nil]);
     2174
     2175        JSScript *script2 = [JSScript scriptOfType:kJSScriptTypeProgram withSource:source andSourceURL:sourceURL andBytecodeCache:cachePath inVirtualMachine:vm error:nil];
     2176        RELEASE_ASSERT(script2);
     2177        NSError* error = nil;
     2178        checkResult(@"Should NOT be able to cache the second file", ![script2 cacheBytecodeWithError:&error]);
     2179        checkResult(@"Should NOT be able to cache the second file has the correct error message", [[error description] containsString:@"Could not lock the bytecode cache file. It's likely another VM or process is already using it"]);
     2180    }
     2181
     2182    NSFileManager* fileManager = [NSFileManager defaultManager];
     2183    BOOL removedAll = [fileManager removeItemAtURL:cachePath error:nil];
     2184    checkResult(@"Successfully removed cache file", removedAll);
     2185}
     2186
     2187static void testCacheFileFailsWhenItsAlreadyCached()
     2188{
     2189    NSURL* cachePath = tempFile(@"foo.program.cache");
     2190    NSURL* sourceURL = [NSURL URLWithString:@"my-path"];
     2191    NSString *source = @"function foo() { return 42; } foo();";
     2192
     2193    @autoreleasepool {
     2194        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
     2195
     2196        JSScript *script = [JSScript scriptOfType:kJSScriptTypeProgram withSource:source andSourceURL:sourceURL andBytecodeCache:cachePath inVirtualMachine:vm error:nil];
     2197        RELEASE_ASSERT(script);
     2198        checkResult(@"Should be able to cache the first file", [script cacheBytecodeWithError:nil]);
     2199    }
     2200
     2201    @autoreleasepool {
     2202        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
     2203
     2204        JSScript *script = [JSScript scriptOfType:kJSScriptTypeProgram withSource:source andSourceURL:sourceURL andBytecodeCache:cachePath inVirtualMachine:vm error:nil];
     2205        RELEASE_ASSERT(script);
     2206        NSError* error = nil;
     2207        checkResult(@"Should not be able to cache the second time because the cache is already present", ![script cacheBytecodeWithError:&error]);
     2208        checkResult(@"Correct error message should be set", [[error description] containsString:@"Cache for JSScript is already non-empty. Can not override it."]);
     2209
     2210        JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
     2211        JSC::Options::forceDiskCache() = true;
     2212        JSValue *result = [context evaluateJSScript:script];
     2213        RELEASE_ASSERT(result);
     2214        checkResult(@"Result should be 42", [result isNumber] && [result toInt32] == 42);
     2215        JSC::Options::forceDiskCache() = false;
     2216    }
     2217
     2218    NSFileManager* fileManager = [NSFileManager defaultManager];
     2219    BOOL removedAll = [fileManager removeItemAtURL:cachePath error:nil];
     2220    checkResult(@"Successfully removed cache file", removedAll);
     2221}
     2222
    21622223@interface JSContextFileLoaderDelegate : JSContext <JSModuleLoaderDelegate>
    21632224
     
    23672428    RUN(testBytecodeCacheWithSameCacheFileAndDifferentScript(true));
    23682429    RUN(testProgramJSScriptException());
     2430    RUN(testCacheFileIsExclusive());
     2431    RUN(testCacheFileFailsWhenItsAlreadyCached());
    23692432
    23702433    RUN(testLoaderRejectsNilScriptURL());
  • trunk/Source/JavaScriptCore/ChangeLog

    r242576 r242585  
     12019-03-06  Saam Barati  <sbarati@apple.com>
     2
     3        JSScript should keep the cache file locked for the duration of its existence and should truncate the cache when it is out of date
     4        https://bugs.webkit.org/show_bug.cgi?id=195186
     5
     6        Reviewed by Keith Miller.
     7
     8        This patch makes it so that JSScript will keep its bytecode cache file
     9        locked as long as the JSScript is alive. This makes it obvious that it's
     10        safe to update that file, as it will only be used in a single VM, across
     11        all processes, at a single time. We may be able to extend this in the future
     12        if we can atomically update it across VMs/processes. However, we're choosing
     13        more restricted semantics now as it's always easier to extend these semantics
     14        in the future opposed to having to support the more flexible behavior
     15        up front.
     16       
     17        This patch also:
     18        - Adds error messages if writing the cache fails. We don't expect this to
     19          fail, but previously we would say we cached it even if write() fails.
     20        - Removes the unused m_moduleKey field.
     21        - Makes calling cacheBytecodeWithError with an already non-empty cache file fail.
     22          In the future, we should extend this to just fill in the parts of the cache
     23          that are not present. But we don't have the ability to do that yet, so we
     24          just result in an error for now.
     25
     26        * API/JSScript.mm:
     27        (-[JSScript dealloc]):
     28        (-[JSScript readCache]):
     29        (-[JSScript init]):
     30        (-[JSScript writeCache:]):
     31        * API/JSScriptInternal.h:
     32        * API/tests/testapi.mm:
     33        (testCacheFileIsExclusive):
     34        (testCacheFileFailsWhenItsAlreadyCached):
     35        (testObjectiveCAPI):
     36
    1372019-03-06  Christopher Reid  <chris.reid@sony.com>
    238
Note: See TracChangeset for help on using the changeset viewer.