I'm using GTMLogger and have the following piece of code that seems to be crashing since moving to iOS5.1 . The weird thing i can't seem to reproduce it, but i know its happening to many users, so I'm not sure how to track it.
[NSConcreteFileHandle writeData:]: Bad file descriptor
I could just try/catch it but its not a solution as much as a workaround.
This is the suspicious part:
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:logFilePath];
if (file)
{
unsigned long long maxFileOffset = [file seekToEndOfFile];
if (maxFileOffset > kDebugFileMaxSize)
{
// read file, trunicate, save, reopen
[file seekToFileOffset:maxFileOffset - kDebugFileMaxSize];
NSMutableData *data = [NSMutableData dataWithData:[file readDataToEndOfFile]];
[file seekToFileOffset:0];
[file writeData:data];
[file truncateFileAtOffset:kDebugFileMaxSize];
[file seekToEndOfFile];
}
}
else
{
[[NSFileManager defaultManager] createFileAtPath:logFilePath contents:nil attributes:nil];
file = [NSFileHandle fileHandleForUpdatingAtPath:logFilePath];
}
Anyone ever got something like that ?
I eventually just got rid of GTMLogger and moved to some other logger, thanks for anyone who might've read this :)
Related
I created an overload method for NSLog in order to write all logs in an HTML file.
All logs are written in the HTML file using GCD.
The problem is that lines are sometimes truncated...
Here my code :
Write in log file function :
+(void)writeInLogFile:(NSString *)strLog inFolder:(NSString *)folder fileName:(NSString *)fileName extension:(NSString *)extension{
//Create Directory
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:folder];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) //Does directory already exists?
{
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error])
{
NSLog(#"%d||Create log directory error: %#",LOG_SEVERITY_HIGH, error);
}
}
NSString* filePath = [NSString stringWithFormat:#"%#/%#.%#",path,fileName,extension];
int nbOfLogFiles = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil] count];
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
[HandleString createLogFile:filePath andStrLog:strLog];
}
else{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
}
}
That is the result :
And the call (called each time an NSLog is executed):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
That is the result :
As you can see, the last line is truncated...
This appens throughout the file.
I tried to run the process on the main thread and it works well without problems.
Another interesting thing when i change the QOS the result isn't the same, for exemple whith priority high, i have more truncated lines.
Edit : The code to write in file :
+(void)createLogFile:(NSString*)filePath andStrLog:(NSString*)strLog{
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
The issue is that you're writing from multiple threads simultaneously. Consider this sequence of events:
Thread A seeks to end of file
Thread B seeks to end of file
Thread B writes its data
Thread A writes its data
Thread A's data will overwrite Thread B's data. If Thread A's data is longer, then no trace of Thread B's data will be left. If Thread A's data is shorter, then the part of Thread B's data beyond that length will remain.
Swapping the order of the last two steps has a similar problem. And there are, of course, more complicated scenarios with more threads.
One solution is to serialize all accesses to the file, as you've done in your self-answer. Another is to open the file in append-only mode. In this mode, all writes are done at the end of the file. This is enforced by the OS. There's no way for two simultaneous writes to overwrite each other. The current file position of the file handle is irrelevant, so there's no need to seek.
NSFileHandle doesn't directly support opening a file in append-only mode. You have to use open() to open a file descriptor and then hand that file descriptor off to NSFileHandle. (You could also just use write() and close(), but it's a bit messier.) For example:
int fd = open(filePath.fileSystemRepresentation, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
// handle error
}
NSFileHandle* fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
[fileHandle release]; // Only needed if not using ARC
Note that you don't have to explicitly create the log file in this case. The open() call will create it if it doesn't exist, because of the O_CREAT flag.
Given the phrase "I tried to run the process on the main thread and it works well without problems." I would say that your truncation problem comes from a buffer being closed before completely empty. That does not explain the disparity you might have observed while using high priority (remember that the extra lines might be coincidental until proven otherwise...).
Now, I would recommend you to try a small sleep before ending the thread scope...
I found a solution a lil bit tricky :
I removed the dispatch_asynch :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
And in my write in log file function i did :
static dispatch_queue_t asyncQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
asyncQueue = dispatch_queue_create("asyncQueue", NULL);
});
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
dispatch_async(asyncQueue, ^{
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
else{
dispatch_async(asyncQueue, ^{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
It could may be help someone.
But i have to know if according to you, it is a good solution?
John
Using the line below,
[fileManager copyItemAtPath:sourcePath toPath:targetPath error:&error];
We can copy a folder but if the folder already exists it throws an exception "File Exists".
In order to overwrite a single file, we can achieve it through the following lines:
NSData *myData = [NSData dataWithContentsOfURL:FileURL]; /fetch single file
[myData writeToFile:targetPath atomically:YES];
But I want to copy an already existing folder i.e, overwrite.
Edit :
Simple Possibility , I can remove the items before copying them.
Please suggest any more possibilities.
The default behavior of NSFileManager method is to throw an exception/error "File Exists." when the file exists. But still if you want to overwrite using NSFileManager then it provides one api for that which is mentioned below replaceItemAtURL as well in first solution:-
Also there are three solutions to achieve that
First Solution
[filemanger replaceItemAtURL:url1
withItemAtURL:url2
backupItemName:#"/Users/XYZ/Desktop/test.xml"
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil error:nil];
Using the above API you can overwrite the file contents. But before that you have to take the backup of your source path in your temporary directory.
Second Solution
Already you have mentioned in your question using NSData writeToUrl.
Third Solution
trojanfoe has mentioned in their answer. i.e. remove the item being overwritten beforehand.
I would like to add one more using delegate, in order to override files with copyItemAtPath (NSFileManager) function use:
[[NSFileManager defaultManager] setDelegate:self];
[[NSFileManager defaultManager] copyItemAtPath:fileOrigin toPath:fileDestin error:&error];
and implement the delegates optional function:
- (BOOL)fileManager:(NSFileManager *)fileManager shouldProceedAfterError:(NSError *)error copyingItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath{
if ([error code] == NSFileWriteFileExistsError) //error code for: The operation couldn’t be completed. File exists
return YES;
else
return NO;
}
Remove the item first, with:
[fileManager removeItemAtPath:targetPath error:NULL];
(i.e. ignoring any error)
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
In an iOS application, I'm generating a 'voice recorder' functionality for continuous capturing of speech.
I use the following code for writing the speech in to a file.
//output speech
NSString *filePath = [root stringByAppendingPathComponent:#"output_speech.raw"];
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
[[NSData data] writeToFile:filePath atomically:YES];
}
NSData *myData = [NSData dataWithBytes:ptrOut length:DataByteSize];
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:myData];
[handle closeFile];
My question is, in case the iOS device is going out of memory, how to handle the file writing situation?
There is a very similar question here:
iPhone: available disk space
I would check for the available disk space and appropriately determine whether to cancel the write and alert the user, or to just silently fail.
#Infinity James, I obtained the free space using the method mentioned in following link. [link] (http://www.linkedin.com/groups/How-detect-total-available-free-72283.S.217861544)
I have a "Write" button that takes whatever in a Textfild, and appends to a designated file. I also have a "Read" button that reads from the content of the designated file, and displays it on the screen. The "Write" button calls writeDataToFile:(id)sender, and the "Read" button calls readDataFromFile:(id)sender.
The problem is that the following simple code works fine on iPhone 6.1 Simulator (Xcode 4.6.2), but does not display anything in a read device, e.g., iPod with iOS 6.1.3.
I suspect the problem is something related to encoding:NSUTF8StringEncoding. However, I tried NSUTF16StringEncoding and NSUTF32StringEncoding, and it still didn’t work on the real device (but disply scrambled words on Simulator).
Where did I do wrong? I would appreciate very much if somebody can give me some pointers.
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
_fileName = [documentsDirectory stringByAppendingPathComponent:#"data.txt"];
}
- (IBAction)writeDataToFile:(id)sender {
NSString *str = tfData.text;
str = [str stringByAppendingFormat:#"\n"];
_fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
[_fileHandle seekToEndOfFile];
//convert NSString to NSData
[_fileHandle writeData: [str dataUsingEncoding:NSUTF8StringEncoding]];
[_fileHandle closeFile];
[tfData resignFirstResponder];
}
- (IBAction)readDataFromFile:(id)sender {
[tfData resignFirstResponder];
//lbOut is a UILabel
lbOut.text = [NSString stringWithContentsOfFile:_fileName encoding:NSUTF8StringEncoding error:NULL];
}
Try to diagnose whether the file exists when you try to read from it. You can do this with NSFileManager. Also check whether the file has the right size (or at least a non-zero size).
Also, have you checked whether stringWithContentsOfFile actually returns a string? If an error occurs it will return nil and not raise an exception. If you get nil you can pass an NSError object to stringWithContentsOfFile and it will let you know what went wrong.
Start by checking your return values. If I had to make a guess I would say that the file at _fileName does not exist. If the file doesn't exist fileHandleForUpdatingAtPath: will return nil. And each subsequent call on the fileHandle will fail silently.
So you should check if fileHandleForUpdatingAtPath: actually returns a fileHandle. If it doesn't, create a fileHandle by using fileHandleForWritingAtPath:.
In a similar way you should check if the conversion to NSData has failed. I used NSParameterAssert, which will crash your app if stringData is nil, because I think there is no way to recover from a failed NSString conversion. If you think there is a way to recover from this error replace the NSParameterAssert with a if (!stringData) statement.
_fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
if (!_fileHandle) {
NSLog(#"File does not exist at path \"%#\". Create it.", _fileName);
_fileHandle = [NSFileHandle fileHandleForWritingAtPath:_fileName];
}
// should have a valid fileHandle here
[_fileHandle seekToEndOfFile];
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSParameterAssert(stringData); // will raise an exception if stringData is nil
[_fileHandle writeData:stringData];
I found the problem. The problem is with the binding of _fileHandle. I declare this public variable, and binds it with a NSFileHandle associated with the file I intended to write using
[NSFileHandle fileHandleForWritingAtPath:_fileName]. I intended to keep using the _fileHandle from different ViewControllers without the need to bind it with the file every time I use it. It resulted the problem I described (i.e., works fine on Simulator, but not on real device).
Once I re-bind _fileHandle with NSFileHandle initialization every time I use it, it works fine now.
I am having the problem of writing large file (>200 MB) in iOS device (iPad) but in the simulator it works perfect.
I am using NSFileManager to create file and NSFileData to write file.
I think there is no problem in my code as it runs fine in the simulator.
Does anyone having the same problem?
To elaborate my situation:
I am saving chunk of files (3MB each) in my device which works fine. That means for a 300 MB file I have 100 chunks. Now, from the 100 chunks I want to create the actual file. So I am using the NSFileManager to create file in first iteration and then using NSFileData to write the 3MB data at the end of the file. While running the program it crashes after 61 chunks. I am guessing there might have some memory related issues in the iPad.
I am saving the chunk of files in fileDir in the format data-0, data-1, data-2...
I am applying the decrypt operation on data but for simplicity I have removed that portion.
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
fileName = [[NSString alloc] initWithFormat:#"data-%d", i];
filePath = [fileDir stringByAppendingPathComponent:fileName];
fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
// Write in tempFile
if(offset == 0){
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:data attributes:nil]){
fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
} else {
[fileWriteHandle seekToEndOfFile]; // Tried with comment out this line but same problem
// Write the decrypted data from chunk
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}
}
Edit (11.02.2013)
I tried with my previous code where I omitted the data decryption part.
Interestingly, the problem was in the decryption part I guess cause without the decryption it works fine. I have added the decryption code. For decryption I am using NSData+CommonCrypto library (it's non ARC) but my project is in ARC.
It could be an operating system issue because the NSFileHandle is never being closed for each chunk. I would recommend closing it.
Also, it looks like you have your variables declared outside the scope of the for loop. Unless you need those variables outside the loop, it's generally good to keep the scope of your variables as small as possible, especially if you are using ARC and are trying to think about when memory will be released.
If you think that the NSFileHandle is holding onto data in memory, try to use the -synchronizeFile method after writing each chunk to make sure in memory changes are reflected to disk.
Also, I moved the creation of the file you are writing to outside the loop, because it's easier to follow for me.
Try this adjustment:
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:[NSData data] attributes:nil]){
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
NSFileHandle *fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
NSString *fileName = [NSString stringWithFormat:#"data-%d", i];
NSString *filePath = [fileDir stringByAppendingPathComponent:fileName];
NSFileHandle *fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
NSData *data = [fileReadHandle readDataToEndOfFile];
// No longer using the file
[fileReadHandle closeFile];
// Write in tempFile
[fileWriteHandle writeData:data];
[fileWriteHandle synchronizeFile];// Flush any data in memory to disk
}
[fileWriteHandle closeFile];
Modifying the following code worked like a magic,
#autoreleasepool {
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}