Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-3536] Memory leak with String.range(options: .regularExpression) #4303

Closed
swift-ci opened this issue Jan 4, 2017 · 7 comments
Closed

Comments

@swift-ci
Copy link
Contributor

swift-ci commented Jan 4, 2017

Previous ID SR-3536
Radar None
Original Reporter djones6 (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Linux (Ubuntu 14.04), Swift 3.0.2 release

Additional Detail from JIRA
Votes 0
Component/s Foundation
Labels Bug, Linux
Assignee nethraravindran (JIRA)
Priority Medium

md5: 42f6ad73e1163758bff04e137daf6d8c

cloned to:

  • SR-3904 NSCache on Linux only works with static keys

Issue Description:

There appears to be a memory leak on Linux when searching for a substring of a String using a regular expression. The following code leaks around 18mb (roughly 1800 bytes per call to String.range):

import Foundation

let myString = "Foo"
for _ in 1...10000 {
  let _ = myString.range(of: "bar", options: .regularExpression)
}

It seems to be specific to the use of regular expressions. Using (for example) options: .caseInsensitive does not leak.
The same code also does not leak on Mac.

@alblue
Copy link
Contributor

alblue commented Feb 8, 2017

Looks like a general leak in NSCache, independent of the type.

{{ 1> import Foundation
2> var c = NSCache<NSString,NSString>()
c: Foundation.NSCache<Foundation.NSString, Foundation.NSString> = {
Foundation.NSObject = {}
_entries = 0 key/value pairs
3> c.countLimit = 10
4> c.setObject("An Object",forKey:"key")
5> c
$R0: Foundation.NSCache<Foundation.NSString, Foundation.NSString> = {
Foundation.NSObject = {}
_entries = 1 key/value pair {

6> for _ in 1...10000 {
7. c.setObject("A string", forKey:"key")
8. } // run this a few times
9> c
$R1: Foundation.NSCache<Foundation.NSString, Foundation.NSString> = {
Foundation.NSObject = {}
_entries = 154101 key/value pairs {
[0] = {
key = {
_rawValue = {
Foundation.NSObject = {}
_cfinfo = {
}}
There shouldn't be 154101 key/value pairs here if we have a count limit of 10.

@alblue
Copy link
Contributor

alblue commented Feb 8, 2017

Here's a view of the cache from the command line, with Swift 3.0.2 on Ubuntu 16.04. There are repeated key/value with the same thing, if they have been set in the cache. I would have expected (a) this not to happen, and (b) even if it did store duplicates for it to start evicting after the countLimit was reached. The hypothesis is that the String.range cache of Regex is hitting this same issue with the cache in some way:

[254] = {
key = {
_rawValue = {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "key"
}
}
value = {
key = {...}
value = {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "A string"
}
cost = 0
prevByCost = nil
nextByCost = nil
}
}
[255] = {
key = {
_rawValue = {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "key"
}
}
value = {
key = {...}
value = {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "A string"
}
cost = 0
prevByCost = nil
nextByCost = nil
}
}
...

@alblue
Copy link
Contributor

alblue commented Feb 8, 2017

Interestingly, it doesnt' look like the lookup works for String values.

> var c = NSCache<NSString,NSString>
> c.setObject("foo",forKey:"foo")
> c.object(forKey:"foo")
$R6: Foundation.NSString? = nil

@alblue
Copy link
Contributor

alblue commented Feb 8, 2017

So if you have two NSString instances, with the same value, then they don't get looked up properly from NSCache:

Welcome to Swift version 3.0.2 (swift-3.0.2-RELEASE). Type :help for assistance.
1> import Foundation
2> let k1:NSString = "key"
k1: Foundation.NSString = {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "key"
}
3> let k2:NSString = "key"
k2: Foundation.NSString = {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "key"
}
4> var c = NSCache<NSString,NSString>()
c: Foundation.NSCache<Foundation.NSString, Foundation.NSString> = {
Foundation.NSObject = {}
_entries = 0 key/value pairs
_lock = {
Foundation.NSObject = {}
mutex = 0x000000000041cc30
name = nil
}
_totalCost = 0
_byCost = nil
name = ""
totalCostLimit = -1
countLimit = -1
evictsObjectsWithDiscardedContent = false
delegate = (instance_type = 0xfffffffffffffff9) {
instance_type = 0xfffffffffffffff9
}
}
5> c.setObject(k1,forKey:k1)
6> c.object(forKey:k2)
$R0: Foundation.NSString? = nil
7> k1 == k2
$R1: Bool = true
8>

If you use the same instance, then it works:

8> c.object(forKey:k1)
$R2: Foundation.NSString? = 0x0000000000448b10 {
Foundation.NSObject = {}
_cfinfo = {
info = 1920
pad = 0
}
_storage = "key"
}
9>

@alblue
Copy link
Contributor

alblue commented Feb 8, 2017

Further observation: the NSCache only works iff the keys are unique. It looks like they are hashing based on the memory address of the key, which means it will work for static constants but not for dynamically generated strings.

open func object(forKey key: KeyType) -> ObjectType? {
var object: ObjectType?

let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)

_lock.lock()
if let entry = _entries[keyRef] {
object = entry.value
}
_lock.unlock()

return object
}

Suggestion would be to remove the caching from the regular expression lookup until a correct implementation can be found.

@swift-ci
Copy link
Contributor Author

swift-ci commented Feb 9, 2017

Comment by Nethra Ravindran (JIRA)

Raised the PR for this issue - #876

@spevans
Copy link
Collaborator

spevans commented Feb 9, 2020

This looks to be resolved since at least 4.2 as no leaks are seen with valgrind anymore

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 5, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants