Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
I ran into a problem tonight. Basically I have 3 classes which 2 are viewControllers and one NSObject. My NSObject Class is called "BoilerPlateCode", it contains methods that I call often from different classes like accessing the Documents folder.

The problem that I have is viewController1 is pushing viewController2 on to the stack before it finishes saving. In viewController1's method viewWillDisappear: I have it call the BoilerPlateCode class to save the data to the Documents folder.

When viewController2 instantiates the viewDidLoad method is called. It then retrieves some of the saved data using the BoilerPlateCode to display on the screen.

The problem that I am having is that viewController2 is being pushed on the stack before VC1 has finished writing the data. The result is that the old data is loaded instead.

I tested this with 2 NSLogs, one writing out to the screen "Saved" from VC1 and the other writing out "Loaded" from VC2.

VC1
Code:
-(void)viewWillDisappear:(BOOL)animated{                    
    if (shouldSaveTextView || ![currentXP isEqualToString:savedXP]) {
        NSString *textFromView = notesField.text;
        [characterLoadInfo setValue:textFromView forKey:@"textView"];
        [characterLoadInfo setValue:currentXP forKey:@"savedXP"];
[COLOR="Red"]        BoilerPlateCode *bpc = [[BoilerPlateCode alloc] init];
        [bpc saveAllCharacter:characterName with:characterLoadInfo];
        NSLog(@"saved");[/COLOR]
   }
}

VC2
Code:
- (void)viewDidLoad{
     BoilerPlateCode *bpc = [[BoilerPlateCode alloc] init];
     NSDictionary *charDict2 = [bpc reloadCharacter:characterName];
    maxHP = [[charDict2 objectForKey:@"hitPoints"]intValue];
    NSString *hp = nil;
    
    if ([charDict2 objectForKey:@"currentHP"]) {
        hp = [charDict2 objectForKey:@"currentHP"]; // If HP hs been modified use this
    }
    else if ([charDict2 objectForKey:@"hitPoints"]){ // If no combat has happened yet then use this
        hp = [charDict2 objectForKey:@"hitPoints"];
    }
    else
        hp = @"0";
    [COLOR="Red"]NSLog(@"loaded");[/COLOR]

}

2013-08-26 20:44:12.721 XP Tracker[7691:15503] loaded
2013-08-26 20:44:12.729 XP Tracker[7691:15503] saved

On the console I should see 'saved' first then 'loaded', but it is the opposite. So I am thinking that when I call the BoilerPlateCode object to perform the save task, it is performing this task as an asynchronous operation. This allows VC1 to push VC2 on the stack and start to load the data before it finishes saving it.

If this is the case is there a way to make it perform the save on the main thread so it has to wait till the save is finished before pushing VC2 on the stack and calling the load information?
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
On the console I should see 'saved' first then 'loaded', but it is the opposite. So I am thinking that when I call the BoilerPlateCode object to perform the save task, it is performing this task as an asynchronous operation. This allows VC1 to push VC2 on the stack and start to load the data before it finishes saving it.

If this is the case is there a way to make it perform the save on the main thread so it has to wait till the save is finished before pushing VC2 on the stack and calling the load information?

Before you go deep into multithreading code, I'd add two more NSLog to check which method is actually _called_ earlier. I suspect that the second view is created, and after that the first view is removed.
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
Thanks gnasher729. I spent a late night and found a work around. Instead of calling the method to save the data in the viewWillDisapear method I moved that data to the button method that calls that instantiates the new VC and I have it save first.

This solved the problem but I am still curious if it is a multi threading problem of not? My gut tells me it is not since I don't use any GCD to handle my class on a separate thread. The main thread should wait till it is finished writing the plist and then push the new VC on the stack. Then reload data it needs from the plist on the new VC.

Thanks again.
 

multinode

macrumors regular
Feb 4, 2011
150
0
Larswick ... I had a similar problem.

You didn't say how you went from View1 to View2 ... I assume you used a segue. Suggestion: don't execute "PerformSegue" until you've checked that your save has been completed ... pseudo code:

Code:
while (!saved); //a barrier sync
PerformSegue;
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
After what gnasher729 said I did some more testing, but i used a UIButton to push the new VC on the stack with this code

Code:
[self.navigationController pushViewController:newCombat animated:YES];

I thought I had solved the problem by adding the code to the UIButton method to save the plist data. But after doing some more testing I moved my VC2 code that loaded data from a plist file to the viewWillAppear method instead of the viewDidLoad method and this solved the problem. Now all the data is being written before parts of it are begin reloaded.

I thought the original problem was multi threading related, but appears not to be.

Thanks
 

multinode

macrumors regular
Feb 4, 2011
150
0
Hello Larswik ...

I see where you moved the loading of saved data to "ViewWillAppear" and you believe that your data loading now takes place AFTER that data is saved ... problem solved. I don't agree ... I think you are winning by accident and that at some point in the future you will be bitten again.

I think that you must use EXPLICIT synchronization such as I suggested with the "while(!saved);" barrier.
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
I do see what you are saying using the while loop till it is competed. But from what I learned in programming class even Object C is 'kind of' a procedural language. What I mean by that is even though a method is called it drops down line by line executing the code till the method exits.

Since I don't dispatch writeToFile: method on a separate thread it should finish the task before proceeding to the next line of code. I was little confused about the order of method calls when you push a new viewController on the stack. There are at least 3 methods called on a new VC when instantiated.
Code:
initWithNibName:
viewDidLoad
viewWillAppear:

and when your old viewController calls the viewWillDisappear:

So I put an NSLog in each method without having any data written to the Documents folder, and then with saving data that I had a problem with to test it.

2013-08-30 23:49:11.323 XP Tracker[8311:15503] initMethod
2013-08-30 23:49:11.344 XP Tracker[8311:15503] viewDidLoad
2013-08-30 23:49:11.344 XP Tracker[8311:15503] viewWillDisappear
2013-08-30 23:49:11.345 XP Tracker[8311:15503] viewWillApear

So going off of gnasher729 reply I ran the test and discovered that I was putting the "LoadMyPlist", in viewController2, in the viewDidLoad method and putting my "saveMyPlist" in the viewWillDisappear method in viewController 1. It then became clear that I was loading my data before I was even saving it. So, if I am right and this is not an asynchronous task it should lock up the iPad until it finishes writing the data.

My hesitation with the while loop comes after reading the docs for the writeToFile: method. That method returns a BOOL and if that method fails to write the data then I created an infant loop which could lock up the program. Not to mention that the while loop 'should' not get executed until the line of code above returns a YES or NO response. So a while loop might not be a good idea to have here to be save.

Code:
BOOL saved = [myDict writeToFile: YES];
while(!saved){
   keep infinite loop going...
}
Please correct me if I am wrong, I am here to learn and enjoy programming.

Thanks and by the way it looks like I accidentally submitted this on the OSX and not the ios forum, sorry.
 

multinode

macrumors regular
Feb 4, 2011
150
0
I do see what you are saying using the while loop till it is competed. But from what I learned in programming class even Object C is 'kind of' a procedural language. What I mean by that is even though a method is called it drops down line by line executing the code till the method exits.

Since I don't dispatch writeToFile: method on a separate thread it should finish the task before proceeding to the next line of code.

Not exactly correct for two reasons:
1. the
Code:
writeToFile
line may be executing asynchronously so that the following line may execute before the write is completed. In fact, clearly things are happening "under the covers" before the write has completed. That's the source of your problem in the first place. ;)
2. compiler optimization may alter the code ordering if the compiler thinks the altered ordering doesn't change your functionality.

I was little confused about the order of method calls when you push a new viewController on the stack. There are at least 3 methods called on a new VC when instantiated.
Code:
initWithNibName:
viewDidLoad
viewWillAppear:

and when your old viewController calls the viewWillDisappear:

So I put an NSLog in each method without having any data written to the Documents folder, and then with saving data that I had a problem with to test it.

So going off of gnasher729 reply I ran the test and discovered that I was putting the "LoadMyPlist", in viewController2, in the viewDidLoad method and putting my "saveMyPlist" in the viewWillDisappear method in viewController 1. It then became clear that I was loading my data before I was even saving it. So, if I am right and this is not an asynchronous task it should lock up the iPad until it finishes writing the data.

My hesitation with the while loop comes after reading the docs for the writeToFile: method. That method returns a BOOL and if that method fails to write the data then I created an infant loop which could lock up the program. Not to mention that the while loop 'should' not get executed until the line of code above returns a YES or NO response. So a while loop might not be a good idea to have here to be save.

Code:
BOOL saved = [myDict writeToFile: YES];
while(!saved){
   keep infinite loop going...
}
Please correct me if I am wrong, I am here to learn and enjoy programming.

Thanks and by the way it looks like I accidentally submitted this on the OSX and not the ios forum, sorry.

I have a basic problem with all of your "solutions" ... assumptions about what Apple is doing "under the covers" ... none of which is documented and thereby guaranteed to not change. :( Do you really know that the ordering of the functions called by Apple will stay that way forever??

You, the programmer must be in control regardless of what Apple does. The
Code:
while (!saved) {
}
does that. Finally, if you are worried about getting hung up in the while loop if "saved" never goes to TRUE, you could put a timer in the
Code:
while(!saved){
}
loop to break out and do something corrective of your choice.
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
2. compiler optimization may alter the code ordering if the compiler thinks the altered ordering doesn't change your functionality.

That is absolutely true, but as a programmer you can completely ignore it, since it will never change functionality.

It is sometimes important for benchmark writers. If the compiler figures out that you do the exact same calculation 100 times (to measure how long it takes), and it doesn't change the functionality whether you do it 100 times or only once, then the compiler may decide to do this only once, making it appear as if the benchmark ran extremely fast.

For example:

Code:
for (i = 0; i < 1000000; ++i) memset (myarray, 0, 1000000);
can be transformed by the compiler into

Code:
memset (myarray, 0, 1000000);
i = 1000000;

which runs about a million times faster.
 

xStep

macrumors 68020
Jan 28, 2003
2,031
143
Less lost in L.A.
I'm surprised there was any question of if writeToFile is asynchronous. It is synchronous. First the docs don't mention it either way. Second the description for the return type clearly indicates its purpose. That purpose can't be achieved with an asynchronous call.


larswik, you figured out your issue was in the sequence of method execution. Problem solved, unless you really think Apple will change that some day.


As for the optimizer. I've had issues with it in the past. Apps working fine with it off, and then acting up with full optimization on. Due to time, we never did track down one of them. I'm trying to dig up some code from over a year ago because this thread has made me curious what that optimization issue was.
 

chown33

Moderator
Staff member
Aug 9, 2009
10,747
8,420
A sea of green
Not exactly correct for two reasons:
1. the
Code:
writeToFile
line may be executing asynchronously so that the following line may execute before the write is completed. In fact, clearly things are happening "under the covers" before the write has completed. That's the source of your problem in the first place. ;)

First, there's no writeToFile: method on NSDictionary. There is a writeToFile:atomically:, so I assume that's what was intended.

Second, I see no logical way that writeToFile:atomically:YES could possibly be asynchronous. From the reference docs:
If flag is YES, the dictionary is written to an auxiliary file, and then the auxiliary file is renamed to path. If flag is NO, the dictionary is written directly to path. The YES option guarantees that path, if it exists at all, won’t be corrupted even if the system should crash during writing.
When the flag is YES, the last operation performed is to rename the aux file. But in order to do that, the write must have completed successfully, otherwise the rename would be wrong. Logically, it's incorrect to replace a prior file with one that wasn't written correctly.

Also, when the flag is YES, it's necessary for the rename to complete successfully, otherwise the return value indicating overall success (a boolean) for the method can't be determined. That is, one can't logically assert YES or NO for overall success without knowing the result of the rename. Why? Because if the rename fails, then the overall success is NO. So clearly, for the return value to be determinate, the rename must be completed.

So when writeToFile:atomically:YES returns, it is definitely completed, and there is no logical way that anything asynchronous can retroactively change the result returned from the method. Any subsequent while loop testing the returned value will never see a different value. It can't possibly do so, if the function isn't being called again. If the function isn't called again, then there's no way the returned value could change.


Since you originally posted pseudo-code, please post an actual worked-out examply of real code that illustrates a synchronizing save barrier. The two-line pseudo-code just isn't enough detail.
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
As for the optimizer. I've had issues with it in the past. Apps working fine with it off, and then acting up with full optimization on. Due to time, we never did track down one of them. I'm trying to dig up some code from over a year ago because this thread has made me curious what that optimization issue was.

The most common reason is code that is broken, but works by coincidence depending on what exactly the compiler does. If it doesn't work without optimisation, then you notice it while you write the code and fix it and think nothing on it. If it doesn't work _with_ optimisation, then you might have problems figuring out where you added the broken code, so you blame the optimiser.

But just in a recent thread someone write something like

Code:
assert (fread (..., n) == n);

and in a non-debug build this doesn't actually call fread!
 

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
larswik, you figured out your issue was in the sequence of method execution. Problem solved, unless you really think Apple will change that some day.

xStep - I just simply meant that all things seem to change and learning to program is a like hitting a moving target. When I started to learn there were no @properties and I had to release anything I NARC'd (New,Alloc, Retain,Copy). Now I code with ARC and I am using Properties more and more.

Even the basics like C under go revisions. Last year when trying to help a friend with some C code I was unable to declare a variable in a for loop. He had an old version of C but I thought C was C.
Code:
for(int i = 0 ; i < 10 ; i++)

We had to declare outside of the for loop for it to work and that is when I discovered even C has revisions.

Code:
 int i;
 for (i = 0; i < 10; i++)

As for my original writeToFile: I did mean writeToFile:atomically:YES I was just using a short hand assuming people who responding would know what I meant.

But as far as my original questions gnasher729 suggestion helped me see what was happening and I added the code to the correct area. I also added this code in the viewWillDisappear:
Code:
    int x = 0;
    while (x != 10000) {
        NSLog(@"Count: %d", x++);
    }

It took a full 5 seconds to print all those NSLogs on the screen before the new view was pushed on the stack. So when a new view is exicuted it first init's everything then viewDidLoad method is called. Then it went back to the viewWillDisappear: on the first VC and executed my while loop which took 5 seconds. Lastly it executed the ViewWillAppear: on the second VC and my second view appeared.

So this cemented the synchronous method writeToFile:atomically:YES would finish before the second view would be pushed on the stack. Even though the second view was loaded, it would not appear before the viewWillDisappear: was executed on the first VC.

Thanks!
 

multinode

macrumors regular
Feb 4, 2011
150
0
I'm surprised there was any question of if writeToFile is asynchronous. It is synchronous. First the docs don't mention it either way. Second the description for the return type clearly indicates its purpose. That purpose can't be achieved with an asynchronous call.

I believe that you're reading too much into the BOOL return. That return merely notes whether or not the file write is SUCCESSFUL. If the file is very long, the write could take a long time. That would mitigate in favor of an asynchronous operation ... it is likely that Apple offloads the file write to another system thread. And the programmer's code continues beyond the write line. I/O is typically done that way because a synchronous operation would block until completion :(

From the docs Apple's ATOMIC seems to mean create and write to another (auxiliary) file in the same destination directory and then replace the original destination with the auxiliary file (renamed to that original file). "Under the covers" Apple might be using locks around the replacing and renaming operations. This meaning of the word ATOMIC doesn't preclude asynchronousity of the file write on another (system) thread.

An asynchronous write could return TRUE or FALSE depending upon whether the operation is SUCCESSFUL. Larswik could easily check my thinking by writing a VERY LARGE test file and putting a counter in
Code:
while (!saved){
int i=0; i<10,000,000; i++);
}
.

Larswick's out of order saving and subsequent loading of data suggests asynchronousity of the file write, but, in truth he hasn't told how much housekeeping is done in his BoilerPlate code prior to the write. That housekeeping code is not necessarily atomic.

Finally, my
Code:
while
does guarantee synchronousity with regard to the saving of his data and prevents the entire operation of launching the 2nd VC until the save is completed. Pretty extreme ... yes. So be it ... Larswik is depending upon the save to be completed prior to his loading.

I still don't like depending upon undocumented coding from Apple ... the company is not known for forthrightness in its changes. I've been bitten by "their code will never change because that would break many apps" ... changed anyway!

A better user experience might be to allow VC2 to appear and REFRESH its data with a notification that executes when SAVED returns TRUE (or do some corrective action if SAVED returns FALSE).
 
Last edited:

multinode

macrumors regular
Feb 4, 2011
150
0
I see no logical way that writeToFile:atomically:YES could possibly be asynchronous. From the reference docs:
If flag is YES, the dictionary is written to an auxiliary file, and then the auxiliary file is renamed to path. If flag is NO, the dictionary is written directly to path. The YES option guarantees that path, if it exists at all, won’t be corrupted even if the system should crash during writing.
When the flag is YES, the last operation performed is to rename the aux file. But in order to do that, the write must have completed successfully (yes, but not necessarily immediately), otherwise the rename would be wrong. Logically, it's incorrect to replace a prior file with one that wasn't written correctly.

Also, when the flag is YES, it's necessary for the rename to complete successfully, otherwise the return value indicating overall success (a boolean) for the method can't be determined. That is, one can't logically assert YES or NO for overall success without knowing the result of the rename. Why? Because if the rename fails, then the overall success is NO. So clearly, for the return value to be determinate, the rename must be completed.

So when writeToFile:atomically:YES returns, it is definitely completed, NO and there is no logical way that anything asynchronous can retroactively change the result returned from the method. Any subsequent while loop testing the returned value will never see a different value (I don't agree). It can't possibly do so, if the function isn't being called again. If the function isn't called again, then there's no way the returned value could change.

Asynchronousity doesn't mean immediate COMPLETION ... it means allowing following code to execute PRIOR to the asynchronous code's completion. I guess I might say immediate RETURN (maybe) but not immediate COMPLETION.
 
Last edited:

chown33

Moderator
Staff member
Aug 9, 2009
10,747
8,420
A sea of green
Asynchronousity doesn't mean immediate COMPLETION ... it means allowing following code to execute PRIOR to the asynchronous code's completion. I guess I could say immediate RETURN (maybe) but not immediate COMPLETION.

It's not possible to allow code following the return to execute without a definite YES or NO. The method does not have a third "indeterminate" state. The boolean is either YES or NO, and it indicates success. It's simply not possible to return a YES unless the entire operation has completed successfully.

If the method returns YES, and the return thereby allows subsequent code to execute, it is completely impossible to change that YES to a NO if the actual saving is completing asynchronously and that saving fails. There is no possible way that executing after the method returns can be "taken back" and started over. There is no speculative execution here once the method returns.

The method is clearly designed for synchronous operation and completion, not asynchronous. The documented return value doesn't say that YES indicates successful enqueueing for eventual completion, it says YES indicates a successful result, period.

If you think speculative or deferred execution is possible, please explain exactly how that would work after the method returns. The nature of returning to the caller is that the caller regains control. There is simply no way to return to the caller while also retaining control. And since returning to the caller requires a definite YES or NO at the point of return, it's logically impossible to defer anything that might cause a YES to become NO or a NO to become YES.

Since you don't agree, it's up to you to explain how this could possibly be done asynchronously. Not a general explanation, but a specific one. Whether Apple does it according to your explanation or not is irrelevant. I assert that it's logically impossible to do what you say, for the given API and the defined semantics of Objective-C message passing.
 

multinode

macrumors regular
Feb 4, 2011
150
0
Ok ... I accept your challenge. :)

Returning immediately, but NOT completing immediately could be effected by offloading the entire writing (and renaming) to a different system thread ... thus allowing Larwik's following code to continue executing. Atomicity doesn't imply synchronousity and Apple's use of the word ATOMIC in the docs doesn't even imply the usual meaning of ATOMIC. An atomic operation means to me that whatever multiple instructions implement that operation, no other of my code can execute inside of the atomic code. My code will execute before or after the atomic code block. But parallelism of the atomic code and my code (on different threads) is not precluded.

Your turn. :)

It's not possible to allow code following the return to execute without a definite YES or NO. The method does not have a third "indeterminate" state. The boolean is either YES or NO, and it indicates success. It's simply not possible to return a YES unless the entire operation has completed successfully. This is true, but an asynchronous operation doesn't complete IMMEDIATELY. Question for you ... what exactly is your understanding of asynchronousity?

If the method returns YES, and the return thereby allows subsequent code to execute no, an asynchronous operation allows following code to execute BEFORE it COMPLETES, it is completely impossible to change that YES to a NO if the actual saving is completing asynchronously and that saving fails. There is no possible way that executing after the method returns can be "taken back" and started over. There is no speculative execution here once the method returns.

The method is clearly designed for synchronous operation and completion, not asynchronous. The documented return value doesn't say that YES indicates successful enqueueing for eventual completion, it says YES indicates a successful result, period.

If you think speculative or deferred execution is possible, please explain exactly how that would work after the method returns. The nature of returning to the caller is that the caller regains control. There is simply no way to return to the caller while also retaining control. And since returning to the caller requires a definite YES or NO at the point of return, it's logically impossible to defer anything that might cause a YES to become NO or a NO to become YES.

Since you don't agree, it's up to you to explain how this could possibly be done asynchronously. Not a general explanation, but a specific one. Whether Apple does it according to your explanation or not is irrelevant. I assert that it's logically impossible to do what you say, for the given API and the defined semantics of Objective-C message passing.
 
Last edited:

chown33

Moderator
Staff member
Aug 9, 2009
10,747
8,420
A sea of green
Ok ... I accept your challenge. :)

Returning immediately, but NOT completing immediately could be effected by offloading the entire writing (and renaming) to a different system thread ... thus allowing Larwik's following code to continue executing. Atomicity doesn't imply synchronousity and Apple's use of the word ATOMIC in the docs doesn't even imply the usual meaning of ATOMIC.

If a system thread is being used to complete the write (and renaming), then Larswik's thread must be blocked, in order to prevent a return to the caller of the method. If Larswik's thread is blocked, then it's not running, and there isn't a return to the caller. This means that no code after the return to caller executes.

If Larswik's thread isn't blocked, then the method can't determine a definite YES or NO to return to the caller, because the operation hasn't completed yet. If it does a spin-lock (e.g a while loop) or it actually suspends the thread is irrelevant. It must not return to the caller until a definite YES or NO is determined. There can be no definite YES or NO until the system thread has completed its operations. So while there may be a system thread completing the operations, Larswik's thread cannot (indeed, must not) return to the caller until the operations are complete. Again, this means that no code after the return to caller executes.

The API for the method simply doesn't have a way to signal completion other than the returned YES or NO value. There isn't any way for the caller to test for completion except by testing the YES/NO value. Since that value is effectively in a CPU register on return (look up the Objective-C binary calling conventions), there's no way that any system thread is going to be able to jump in and change a YES to a NO or vice versa. Since you apparently think otherwise, please explain exactly how that happens. Post actual example code showing not only the test of the returned YES/NO value, but what happens in the loop that is related in any way to an asynchronous completion you're postulating.


As to my understanding of asynchronicity, I've worked on various versions of the Unix kernel starting with Unix v7. I have also written several different multitaskers from scratch: cooperative, pre-emptive, prioritized, etc. for different CPUs such 6809, Z-80, 68K family, Intel x86 family, Atmel AVR, and probably some I've forgotten. I've worked on hard real-time systems, soft real-time systems, and some where real-time was just a faint hope.

I don't consider blocking a thread, performing the operation on another thread, then waking the original thread to be truly asynchronous. It certainly isn't asynchronous from the calling thread's point of view, because no other code runs in that thread until the calling thread is unblocked. It may be asynchronous as far as other threads are concerned, but no other threads are executing the code that follows the return in the original thread. So to the caller of the method, the method effectively completes synchronously.

Note that I'm not saying that disk caches are necessarily flushed before returning to the caller. That's unnecessary, since any other thread (or the same thread) reading the file from disk will simply find the data in the disk cache. The disk and its cache is acting as a thread-safe storage for the data.


EDIT
You added:
An atomic operation means to me that whatever multiple instructions implement that operation, no other of my code can execute inside of the atomic code. My code will execute before or after the atomic code block. But parallelism of the atomic code and my code (on different threads) is not precluded
That description of atomic is irrelevant.

Read the docs for the method. It explains exactly what the flag means. You're making a grave mistake by inferring or imputing that anything else atomic is happening, simply based on the name of the method.

The operation on the file is atomic in the sense that if any error occurs during writing or renaming, then any existing file is left undisturbed and NO is returned. The entire operation completes successfull, or it doesn't. There is no intermediate or indeterminate state of partial completion. Think of it more like a COMMIT transaction on a database, where all operations of a transaction must succeed, otherwise the original state is reinstated.
 
Last edited:

multinode

macrumors regular
Feb 4, 2011
150
0
Hello Chown33 ...

Sounds like you have good experience ... perhaps more than mine. I was the Exec. Director of The PARALLEL Processing Connection for many years in Silicon Valley and worked on NUMA systems implemented by SCI (Scalable Coherent Interface). At the hardware level we considered Sun's M-Bus system. So I'm familiar with atomicity down to the bus operation level.

Beyond our good backgrounds, though, you haven't answered my question: what is an asynchronous file operation?

Atomicity means that a given code operates as a single indivisible operation regardless of how many underlying assembly instructions are involved. Apple's docs referring to ATOMICITY simply tell me the extent of that indivisibility. In fact, it's only their USE of the word ATOMIC that implies indivisibility at all.

I think you are seeing return and completion as being identical ... I don't agree with that. I'm willing to be wrong ... show me.


From http://msdn.microsoft.com/en-us/library/windows/desktop/aa365683(v=vs.85).aspx:
There are two types of input/output (I/O) synchronization: synchronous I/O and asynchronous I/O. Asynchronous I/O is also referred to as overlapped I/O.

In synchronous file I/O, a thread starts an I/O operation and immediately enters a wait state until the I/O request has completed. A thread performing asynchronous file I/O sends an I/O request to the kernel by calling an appropriate function. If the request is accepted by the kernel, the calling thread continues processing another job until the kernel signals to the thread that the I/O operation is complete. It then interrupts its current job and processes the data from the I/O operation as necessary.

Of course, I don't know exactly how Apple implements asynchronous file ops ... I merely suggested the offloading to a system, i.e. kernel thread.
 
Last edited:

multinode

macrumors regular
Feb 4, 2011
150
0
Read the docs for the method. It explains exactly what the flag means. You're making a grave mistake by inferring or imputing that anything else atomic is happening, simply based on the name of the method.

I agree ... I was merely giving ground to you by allowing the name of of the method to imply more than was explicitly being said in the docs.
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
Of course, I don't know exactly how Apple implements asynchronous file ops ... I merely suggested the offloading to a system, i.e. kernel thread.

If you want asynchronous file operations, you just call a synchronous operation on a background thread. That's why you won't find any I/O operations that are asynchronous by themselves.

----------

The operation on the file is atomic in the sense that if any error occurs during writing or renaming, then any existing file is left undisturbed and NO is returned. The entire operation completes successfull, or it doesn't. There is no intermediate or indeterminate state of partial completion. Think of it more like a COMMIT transaction on a database, where all operations of a transaction must succeed, otherwise the original state is reinstated.

Or the app crashes, or someone unplugs the power cable, and the operation is either performed completely or the existing file is left undisturbed, but the method doesn't manage to return either YES or NO :D
 

multinode

macrumors regular
Feb 4, 2011
150
0
If you want asynchronous file operations, you just call a synchronous operation on a background thread. That's why you won't find any I/O operations that are asynchronous by themselves.

So, you are asserting that I, the coder, don't ever (across the entire computer industry) explicitly call for an asynchronous I/O ... big assertion. You are saying that I, the coder, can only explicitly call for synchronous I/O on a background thread.

Microsoft doesn't seem to agree with you ... from http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx::

In some cases, this delay (with synchronous I/O) may be unacceptable to the application's design and purpose, so application designers (that's us making the choice) should consider using asynchronous I/O with appropriate thread synchronization objects such as I/O completion ports.

A process opens a file for asynchronous I/O in its call to CreateFile by specifying the FILE_FLAG_OVERLAPPED flag in the dwFlagsAndAttributes parameter. (That's code we write.) If FILE_FLAG_OVERLAPPED is not specified, the file is opened for synchronous I/O. When the file has been opened for asynchronous I/O, a pointer to an OVERLAPPED structure is passed into the call to ReadFile and WriteFile. When performing synchronous I/O, this structure is not required in calls to ReadFile and WriteFile.

See also asynchronous I/O in http://publib.boulder.ibm.com/infoc...?topic=/com.ibm.xlf101a.doc/xlfopg/asynio.htm

and http://man7.org/linux/man-pages/man7/aio.7.html

Even if you are right, the code on the front thread continues to run while the background thread blocks on its synchronous I/O. Whether I, the app coder, explicitly put a synchronous I/O on a background thread or the system does it, etc. is an implementation issue.
 
Last edited:

Catfish_Man

macrumors 68030
Sep 13, 2001
2,579
2
Portland, OR
If you want asynchronous file operations, you just call a synchronous operation on a background thread. That's why you won't find any I/O operations that are asynchronous by themselves.

Synchronous IO on a background thread is somewhat problematic, actually. Doing it on a global queue invites running into the GCD thread cap (64 threads), and even if you don't manage that it's wasting resources.

dispatch_io provides a proper asynchronous IO API, which is also available in a few places in higher level APIs (NSFileHandle's -readInBackgroundAndNotify: uses it, for example).
 

xStep

macrumors 68020
Jan 28, 2003
2,031
143
Less lost in L.A.
I believe that you're reading too much into the BOOL return. That return merely notes whether or not the file write is SUCCESSFUL.

I read the contract (documentation) and without further evidence or speculation have chosen the only reasonable path which is to believe it.


If the file is very long, the write could take a long time. That would mitigate in favor of an asynchronous operation ... it is likely that Apple offloads the file write to another system thread. And the programmer's code continues beyond the write line. I/O is typically done that way because a synchronous operation would block until completion.

Correct, the method we are discussing blocks the caller until the call has completed. It is a typical file I/O call.


So, you are asserting that I, the coder, don't ever (across the entire computer industry) explicitly call for an asynchronous I/O ... big assertion.

You are the one jumping to that conclusion. We're in an Apple programming oriented thread. Why assume more than the domain we are in?
 

chown33

Moderator
Staff member
Aug 9, 2009
10,747
8,420
A sea of green
Beyond our good backgrounds, though, you haven't answered my question: what is an asynchronous file operation?

A synchronous design is one where return implies completion. An asynchronous design is one where return does not imply completion, that is, "return" is separate from "complete".

An example of a synchronous design is the classis Standard I/O <stdio.h>. All its functions are designed so that a return to the caller implies that the operation embodied by the function are complete, for whatever definition of "complete" is appropriate to the function and the stream. "Complete" doesn't necessarily mean "flushed to disk", because stdio is explicitly designed for in-process buffering.

Because an asynchronous design separates "return" and "complete", they must have a separate mechanism for indicating completion. There are different ways of doing this, such as the real-world examples described here:
http://en.wikipedia.org/wiki/Asynchronous_I/O

Since an asynchrnous design is one that separates "return" and "complete", for any practical purpose, a synchronous design is a design where return is the mechanism that indicates completion.

An asynchronous design without a mechanism for discovering or indicating completion would be extremely difficult to use. So much so that I suspect there are no such designs. However, I could be wrong, so if you know of one, or can explain how such a design would work, please describe it. That is, it would provide the same API and semantics as a synchronous design, e.g. stdio, but would actually be fully asynchronous.

Whether the completion mechanism for async is a callback, a completion routine, a signal, etc., there is always a mechanism other than return. So one could reasonably infer that if a design has no separate mechanism for discovering or indicating completion, then that design isn't asynchronous. In other words, the lack of separation between return and completion is exactly what makes a design synchronous.

Now refer to the writeToFile:atomically: method. What does its design tell you? First, it takes two arguments. Neither of these is a completion routine, callback, or any other type of completion handler or indicator. Second, it returns one value, a boolean with two valid states: YES and NO. The meaning of these states indicates the success or failure of the embodied operation. There is no other state that could indicate "incomplete", nor is it possible to determine a valid YES or NO unless the operations are complete, so the return value is clearly intended as a synchronous design, where return implies completion.

There is no other logical alternative for this method, given the design of C, Objective-C, and the binary calling conventions used. In particular, there is no way that the returned YES or NO value can be changed after the method returns. Neither C nor Objective-C works that way. It's impossible for code that would execute after the return to execute while the method is completing asynchronously. The simple fact is there's nothing in the design of this method that separates completion from return, therefore it must be synchronous.

If there's some way to implement this writeToFile:atomically: method that allows it to return YES or NO, and then later revoke that returned value, then please explain exactly how that would work, using actual code rather than pseudo-code. I assert that it's impossible to do this using C or Objective-C. In fact, I think it would take an extraordinary language to express this "return then revoke" capability.

----------

If you want asynchronous file operations, you just call a synchronous operation on a background thread. That's why you won't find any I/O operations that are asynchronous by themselves.

Also see the O_NONBLOCK option to open(2). There are also non-blocking semantics available for read(2), write(2), etc. And see fcntl(2), and the SIGIO and SIGURG signals.

Furthermore, OS X supports the aio_* family of functions. See 'man aio'.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.