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-8101] App crash with memory corruption on property set #50634

Closed
swift-ci opened this issue Jun 25, 2018 · 21 comments
Closed

[SR-8101] App crash with memory corruption on property set #50634

swift-ci opened this issue Jun 25, 2018 · 21 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Jun 25, 2018

Previous ID SR-8101
Radar None
Original Reporter virl (JIRA User)
Type Bug
Status Resolved
Resolution Invalid
Additional Detail from JIRA
Votes 0
Component/s
Labels Bug
Assignee None
Priority Medium

md5: c314756564702bd11536eb04accf901f

Issue Description:

Swift crashes due to memory corruption on property assignment+read from multiple threads.

Following command-line program crashes with EXC_BAD_ACCESS after minute run on multicore system (Macbook Pro 15" Middle 2015, macOS 10.13.5 (17F77), Xcode 9.4.1 (9F2000)):

import Foundation

class Bar
{
    let baz = "Hello world!"
}

class Foo
{
    var bar = Bar()
}

let globalFoo = Foo()

class VolatileSwift
{
    func start()
    {
        for _ in 1...100 {
            launchReader()
        }
        
        for _ in 1...100 {
            launchWriter()
        }
        
        let _ = readLine()
    }
    
    private func launchReader()
    {
        let thread = Thread {
            while(true) {
                autoreleasepool(invoking: { () -> Void in
                    let baz = globalFoo.bar.baz
                })
            }
        }
        thread.start()
    }
    
    private func launchWriter()
    {
        let thread = Thread {
            while(true) {
                autoreleasepool(invoking: { () -> Void in
                    globalFoo.bar = Bar()
                })
            }
        }
        thread.start()
    }
}

Same code as github repo: https://github.com/virl/VolatileSwift

Or trimmed-down version that still produces the issue:

import Foundation

class Bar {
    let baz = "Hello world!"
}
class Foo {
    var bar = Bar()
}

let globalFoo = Foo()

for _ in 1...100 {
    Thread {
        while true {
            autoreleasepool(invoking: { () -> Void in
                globalFoo.bar = Bar()
            })
        }
        }.start()
}
VolatileSwift(46329,0x70000bcd6000) malloc: *** error for object 0x10286dce0: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug

This is in contrast with Java, Obj-C and C#, where app can't crash just from object property set alone.

Such lack of consistent memory model and multithreading memory guarantees makes Swift unsuitable for any serious server-side, driver or networking development.

@swift-ci
Copy link
Collaborator Author

Comment by Helge Heß (JIRA)

The crash happens because you use tabs to indent your code. Use a proper 2-space indent, and you'll be fine!

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) No, proper indent is tabs because it separates code model from its presentation. Using tabs, you can set viewable indent level in IDE for each team member on individual basis.

But I hope you also have some thoughts about the ticket's issue too.

@swift-ci
Copy link
Collaborator Author

Comment by Helge Heß (JIRA)

Well, wrt the ticket: This is not in contrast with Java. You also need to use a lock or the synchronized keywords to access slots from multiple threads. You can read more about threading in Java over here: https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

Like in Java, the solution to your issue is to use proper locks to serialise access to the variables in question. (and use a proper indent).

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) You're wrong, in Java writing to the object field is atomic operation and it cannot lead to exception or crash by itself, even if it is done (+read) by multiple threads at once.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) Java even gives you guarantees like that you can write to different elements of the same byte/int/long/object array (byte[], Foo[]) from multiple threads (each writing to its own elements) and at the end it will have correct data without crashes.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) Also I would like to know how is it possible that syntactically and API-usage correct Swift code transitioned Swift runtime to such a state that it have to crash entire application process (with potentially tens of thousands served users/clients in other threads of that process) instead of throwing local thread error?

In Java such situation is impossible.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) Also not only Java, but even C# has the same memory guarantees:
https://stackoverflow.com/a/59430/1449965
"Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.
as found in C# Language Spec."

"Atomic" here means that writing/reading to/from field can never lead to crash or partial pointer state, which is exactly what this issue is about.

@swift-ci
Copy link
Collaborator Author

Comment by Helge Heß (JIRA)

You can trim down your sample way more:

import Foundation

class Bar {
  let baz = "Hello world!"
}
class Foo {
  var bar = Bar()
}

let globalFoo = Foo()

for _ in 1...100 {
  Thread {
    while true {
      globalFoo.bar = Bar()
    }
  }.start()
}

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) Thanks, I've included it in ticket description. It is even more funny, because now just only writes crash entire process.

@swift-ci
Copy link
Collaborator Author

swift-ci commented Jun 25, 2018

Comment by Helge Heß (JIRA)

As you would expect the same code crashes with Objective-C.

#import <Foundation/Foundation.h>

@interface Bar : NSObject
{
  NSString *baz;
}
@end

@implementation Bar
- (id)init {
  self = [super init];
  baz = @"Hello World";
  return self;
}
@end

@interface Foo : NSObject
{
  @public
  Bar *bar;
}
@end

@implementation Foo
- (id)init {
  self = [super init];
  bar = [Bar new];
  return self;
}
@end

Foo *globalFoo;

int main() {
  globalFoo = [Foo new];
  
  for (int i = 0; i <= 100; i += 1) {
    [NSThread detachNewThreadWithBlock:^{
      while (YES) {
        globalFoo->bar = [Bar new];
      }
    }];
  }
  sleep(10000);
  
  return 0;
}

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

helge (JIRA User) No, it is not, because it is not the same code — you haven't declared property, you're writing to the field.
Declare property via

@belkadan
Copy link
Contributor

Helge, please don't insult people's code style on an unrelated bug report, even jokingly. It's against the goals of creating a welcoming community.

Vladimir, Helge is correct. Swift does not provide atomic access to global variables by design. [EDIT: Helge pointed out separately that I misread the example. But neither global variables nor properties have a built-in atomic access mode.] There are a handful of reasons for this, with a large one being the use of ARC instead of garbage collection, but it's something that's very unlikely to change.

(You can use tools like Thread Sanitizer to help catch these issues, but you're right that it's still a danger that some other languages don't have.)

@swift-ci
Copy link
Collaborator Author

Comment by Helge Heß (JIRA)

You make them atomic by adding a lock, which you can very well do in Swift too. Presumably in Swift they are always non-atomic because no one really uses atomic properties in Objective-C either (too expensive, rarely do the thing you actually want).

No one debates that Swift is a lower level language than C#/Java - one more reason to use it instead of those ;-)

@belkadan
Copy link
Contributor

Again, let's stop with the value judgments, please.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

@helge No, in Obj-C properties are atomic by default. Yes, they are implemented via spinlocks, but in Swift you have to do it manually.

And atomic properties are not expensive compared to other components of app performance like algorithms.

Swift is positioned as higher level language than Java and C#, with closures and the like.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

@belkadan Welcoming community is where you don’t try preemptively crackdown on friendly funny comments without any offended victim.

And where you don’t close issues as invalid when discussion is just started.

I guess I’l go twitter to the discussion with Doug Gregor, there are more freedom of opinion here: https://twitter.com/shipilev/status/1011354056919519232?s=21

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

@belkadan helge (JIRA User) Runtime should not be able to become corrupted. It should instead provide framework of memory guarantees to the app.

Concurrency is not opt-out from memory safety.

Imagine that you load plugins to the app written in swift. They should not be able to corrupt runtime, concurrently or not.

@swift-ci
Copy link
Collaborator Author

Comment by Helge Heß (JIRA)

virl (JIRA User) Well, this is the bug reporter, not a discussion forum. You reported a bug which @belkadan properly closed as as-intended/designed. If you want to have a discussion on whether that choice is good or not, you should bring it up in the Swift forums, which are there exactly for that: https://forums.swift.org. Or, if you are super-motivated and convinced, you might even want to go straight for a Swift Evolution proposal: https://github.com/apple/swift-evolution

(I'm motivated to reply to your comment, but it really doesn't belong here. It is not a bug, it is an (intended) feature - I think 😉 )

@belkadan
Copy link
Contributor

I hate to put it in such blunt terms, but in Swift concurrency is an opt-out from memory safety. Objective-C's atomic properties do give you memory safety for object references, but they don't give you any other kind of thread safety, and they don't give you memory safety for other types. So we deliberately decided not to provide a similar feature in Swift, even at the cost of memory safety; almost any thread-safe program would need higher-level synchronization anyway.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

@belkadan Static typing too gives you SOME program correctness, but it doesn’t make your program bug free. Why use it then?

Multithreading bugs are always present due to complexity of MT. It’s a question of do you provide dependable framework for borders within which bugs will live, or think that each developer will handle his own infestation.
History shown that second (C lang) approach doesn’t work.

With your approach Swift is unfit for the server, because it doesn’t allow for partially failing systems within single process EVEN when writing them in pure swift code.

This means that game servers and application servers with thousands of concurrent clients will use Java/Kotlin for implementation.

In iOS realities your approach will lead to situation where apps will crash sometimes due to bugs in third-party frameworks they can’t control (even if they are 100% swift) without any possibility to block crashes by the app.
On Android there at least Services and OS processes available for that.

@swift-ci
Copy link
Collaborator Author

Comment by Vladimir Shabanov (JIRA)

@belkadan helge (JIRA User) Later it will be funny to watch complaints of server developers when Swift on their Linux servers will constantly crash entire production server app with thousands of connected clients due to vanishing multithreading bug in one of the client threads. Especially when they discover that tools that analyse such bugs in runtime exist only for macOS, which doesn't work on proper servers anyway.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.
Projects
None yet
Development

No branches or pull requests

2 participants