Another PDFKit bug

In my ongoing saga with PDFKit, a user had a crash that I couldn’t reproduce and generously sent me the PDF that caused the issue. I was immediately able to reproduce the problem and furthermore, managed to get Preview to crash on that PDF. The crash was occurring when I tried to generate the thumbnail for the PDF.

Here’s what doesn’t work on all PDFs:

PDFDocument *pdfDoc = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
int thumbnailPage = [[self valueForKey:@"thumbnailpage"] intValue];
int pdfPageCount = [pdfDoc pageCount];
if (thumbnailPage < pdfPageCount && pdfPageCount > 0 && thumbnailPage >= 0)
{
	PDFPage *page = [pdfDoc pageAtIndex:thumbnailPage];
	if (page != nil)
	{
		NSData *data = [page dataRepresentation];
		if (data)
		{
			image = [[NSImage alloc] initWithData:data];
		}
	}
}

[pdfDoc release];

Here’s what works on all PDFs I’ve tested including the one in question:

NSPDFImageRep *pdfRep = [[NSPDFImageRep alloc] initWithData:
	[NSData dataWithContentsOfURL:[NSURL fileURLWithPath:fullPath]]];
if (pdfRep)
{
	int thumbnailPage = [[self valueForKey:@"thumbnailpage"] intValue];
	int pdfPageCount = [pdfRep pageCount];
	if (thumbnailPage < pdfPageCount && pdfPageCount >= 0 && thumbnailPage >= 0)
	{
		_DebugLog(@"thumbnail page count is valid");
		[pdfRep setCurrentPage:thumbnailPage];
		image = [[NSImage alloc] initWithSize:[pdfRep bounds].size];
		[image addRepresentation:pdfRep];
		[image setDataRetained:YES];
	}
	[pdfRep release];
}

So what does this tell me? It tells me that NSPDFImageRep handles PDFs better than PDFDocument/PDFPage does. I fixed that crash and optimized some other code while I was at it. This still doesn’t solve all my problems with PDFKit as the PDF in question crashes when I try to write metadata to it (it crashes Preview as well). Not much I can do about that, yet.

Update:Funny, when I put this code in a test application, it no longer crashes. I still believe something is wrong with PDFKit as the crash looks very suspicious:

Thread 0 Crashed:
0   libSystem.B.dylib             	0x93287474  
tiny_malloc_from_free_list +
548
1   libSystem.B.dylib             	0x93280ba8 szone_malloc + 200
2   com.apple.CoreFoundation      	0x92342414 __CFDictionaryGrow + 200
3   com.apple.CoreFoundation      	0x92342d64 CFDictionarySetValue + 252
4   libCGATS.A.dylib              	0x95265440  
get_name_with_name_code_nl + 532
5   libCGATS.A.dylib              	0x952654cc  
ats_name_handler_copy_full_name + 20
6   libCGATS.A.dylib              	0x95266ad0 copy_full_name + 36
7   com.apple.CoreGraphics        	0x919b1fd0 CGFontNameTableCreate +  
252
8   com.apple.CoreGraphics        	0x919b1e9c CGFontGetPostScriptName  
+ 36
9   libPDFRIP.A.dylib             	0x068fba64  
PDFFontType1EmitDefinition + 60
10  libPDFRIP.A.dylib             	0x068f8678 PDFFontEmitDefinitions +  
52
11  libPDFRIP.A.dylib             	0x068f9aa8 emitFontDefinition + 20
12  com.apple.CoreFoundation      	0x923743e0 CFSetApplyFunction + 284
13  libPDFRIP.A.dylib             	0x068f9b0c  
PDFFontSetEmitDefinitions + 68
14  libPDFRIP.A.dylib   
        	0x068f68e0 PDFDocumentFinalize + 312
15  libPDFRIP.A.dylib             	0x068f5314 pdf_Finalize + 24
16  com.apple.CoreGraphics        	0x919e5914  
CGContextDelegateFinalize + 72
17  com.apple.CoreFoundation      	0x92371840 _CFRelease + 216
18  com.apple.CoreGraphics        	0x919e58f4  
CGContextDelegateFinalize + 40
19  com.apple.PDFKit              	0x914f77d4 - 
[PDFPage(PDFPageInternal) writeToConsumer:] + 444
20  com.apple.PDFKit              	0x914f6a28 -[PDFPage  
dataRepresentation] + 84
21  ...gtenterprises.receiptwallet	0x0001b8dc -[ReceiptMO  
imageWithWritingThumbnail:] + 988

Defective dog

Our dog had his root canal yesterday performed by Dr. Brook Niemiec. Everything went as planned, except after the initial exam, the vet showed us that Marley had an extra, useless tooth that was encroaching on his front teeth. So, that tooth had to be removed which made the $2000 dog bone even more expensive. Other than Marley being on some drugs for a week and having to eat soft food for 2 weeks, he seems to be doing fine and will be back to his normal self soon. As Marley has been a part of our lives for 3 years, I was very worried about the procedure as he had to be anesthetized. This may be a routine procedure, but anesthesia scares me. I’ve asked for copies of the X-rays and once I get them, I’ll put them up. I figure that for what I paid to repair Marley’s teeth, I might as well let others look at the pictures.

Threading, a necessary evil

Anyone I know that really has a clue avoids multi-threading programming (except for some server applications) as there are so many gotchas. Making things thread safe sounds easy, but is extremely hard as it is quite easy to overlook an item or two I got bit by this in ReceiptWallet in 2 spots. In one case, I build thumbnails on a separate thread to keep the main thread (where the user interacts) running. The problem is that if the user does something, i.e. remove a page or change metadata, I clear the underlying document in the main thread. So the secondary thread tries to use the document that no longer exists and quickly crashes. The fix wasn’t difficult; I simply had to know when the secondary thread was running and don’t change things on the main thread until the secondary thread exited. Of course, I could never reproduce this (like many thread related bugs, it is nearly impossible to track down the cause), so figuring this out was a bit problematic.

The second thread related issue was a little easier to track down, but a bit less straightforward that I messed up. In this case, I used something like:

	[self performSelector:@selector(saveChanges) withObject:nil afterDelay:0.0];

and then proceeded to do:

	NSTask *mdImportTask = [[NSTask alloc] init];
	[mdImportTask setLaunchPath:@"/usr/bin/mdimport"];
	[mdImportTask setArguments:[NSArray arrayWithObject:path]];
	[mdImportTask launch];
	[mdImportTask waitUntilExit];
	[mdImportTask release];

The problem here is that the waitUntilExit runs the main loop until the task (which is actually a separate thread) completes. So saveChanges actually fires when the task is running. The task is in a function that can get called by saveChanges so things become messy quite fast. I had to re-work some code to fix this.

I worked on a project awhile ago that was heavily threaded and despite reassurances that the code was thread safe, it was a royal mess to track down bugs. While I use threads in a number applications I write, I write to avoid them as much as possible, except in cases where doing stuff on the main thread would make the UI unresponsive.

Cocoa bindings causing dealloc to not be called?

In tracking down a bug in ReceiptWallet, I discovered some end (at least to me) odd behavior. ReceiptWallet is an NSDocument based application. When the document is closed, is should call dealloc to release its memory. This wasn’t happening. It appears that some things in my code prevented dealloc from being called; I had to unbind some Cocoa bindings (not all of them, however):

	[_receiptsArrayController unbind:@"selectionIndexes"];
	[photoSizeSlider unbind:@"value"];
	[self unbind:@"currentSelectedIndexes"];

and then I had to remove some observers:

	[collectionsTreeController removeObserver:self forKeyPath:@"selectionIndexPaths"];
	[userDefaultsController removeObserver:self
		forKeyPath:@"values.Show Details With Thumbnails"];

and to top it off, I had to set the view on a toolbar item (a search item) to nil:

	extern NSString *SearchToolbarItemIdentifier;
	NSArray *items = [[[self window] toolbar] items];
	NSEnumerator *itemEnumr = [items objectEnumerator];
	NSToolbarItem *item = nil;
	while (item = [itemEnumr nextObject])
	{
		if ([[item itemIdentifier] isEqualToString:SearchToolbarItemIdentifier])
		{
			[item setView:nil];
			break;
		}
	}

I can understand removing the observer for user defaults, but the bindings have me confused as the bindings are for items that should get released anyway. I’m probably missing something simple, but I’m glad I figured this one out.

Tracking down bugs

One of the toughest parts of my job is tracking down bugs that either I create, are operating system bugs, or are a combination. Most of the bug reports I get are extremely incomplete and don’t help me. I also get crash reports sent to me so that I can try to see the problems. With the crash reports, some users put in a sentence saying what they were doing. Unfortunately, this doesn’t usually help me find the problem and fix it.

Since releasing ReceiptWallet 2.0, I’ve gotten more crash reports than I would like; I’ve fixed some of the issues, but a few have eluded me. Last night I tackled reports dealing with rearranging collections; while I couldn’t reproduce it (and no one provided enough information to help me), I reworked some code and am much happier with that chunk of code. Will it fix the problem? I’m not sure. Today I looked at another crash report dealing with turned on a preference. All of the reports related to this said “turned on show details with thumbnails”. The crash reports made no sense to me as they were in Apple’s code and not mine. I spent about 20 minutes trying a bunch of combinations to see if I could reproduce it. I was making no headway when I decided to open 2 libraries. Still no crash, then I closed one library, hit the preference and boom, crash. Yeah, I tried again and had the same result. So fixing it would be easy. It actually wasn’t that hard to fix it, but when I started digging, I encountered another issue where closing a library never released the memory (dealloc wasn’t being called), but that’s another story.

The more complete the bug report and having reproducible steps makes my life so much easier. For one project I had more than a decade ago, I was told “make this not crash”. OK, easy, right? No, it took me 4 months of 8 hours a day to track down the issue (a bug in the ethernet driver). Was it worth it? Probably, I was working on a prototype system that was used to launch CDMA packet data.

Software is never done, it’s just shipped

In today’s world of consumer software, the phrase “software is never done, it’s just shipped” is the norm. Prior to the wide use of the Internet and downloadable updates, this wasn’t always the case. When it cost real money to send out updates on media, software was tested more, but also people didn’t expect updates as often. When I released ReceiptWallet 2.0 last week, I knew there was going to be an update. I released 2.0.1 this past Sunday and followed it up almost immediately with 2.0.2 b1 as I found more issues.

This phrase may sound like a cop out, but I believe that it is the only way to write software. There are far too many variables to produce a product and have it work everywhere. There are some many different models of computers, various operating systems, hacks that modify the system, different applications that produce PDFs, etc. 10+ years ago, the combinations were far fewer, so it was easier to fully test a product. It is just not practical or even realistic to believe that anyone can ship consumer software that is bug free. Notice I said consumer software; if you have complete control (or significant control) over all the variables like the software in a microwave or a cordless phone, shipping bug free software is possible.

Having said that, I personally like more updates rather than big, infrequent updates. As a developer, this lets me get out fixes to those that need them and as a user, I like to see that developers are actively working on the product.

I’m a huge fan of the Sparkle framework and think that all programs should have this type of update mechanism built in. It makes it simple for users to get the updates and automatically install them. So, pushing out updates is quite easy.

The tax man taketh

It is time for me to pay my taxes again. As someone that is self-employed, I have to pay quarterly estimated taxes, so this happens 4 times a year. One of the things that gets me (it’s a mental thing) is paying taxes in such huge chunks. Salaried employees have taxes withheld and in theory, the total withholdings equals the total tax, so the money comes out in small increments and people don’t end up with a huge tax bill. Being self-employed, my income varies each year. If I have a good year, I end up with a big fat bill. If I have a year worse than the previous year, I get a refund.

Enough about the technical side of paying taxes. When I write the check (figuratively, not literally as I pay my taxes online so the check doesn’t get lost in the mail), I always think about what my taxes buy me. Really, nothing that helps me. You might be asking, what about the roads I drive on? Or the police? Or the fire department? OK, I’ll give you that, but isn’t some of that funded by sales tax, property taxes, and state income tax? What is the federal government giving me? Here’s my list:

  • 1 war in Iraq
  • 1 war in Afghanistan
  • 1 partially finished border fence
  • 1 bailout of Bear Stearns
  • 1 bailout of “greedy” lenders and borrowers (hey, the news used the word “greedy” last night to describe them)
  • 1 fleet of very, very expensive presidential helicopters (now costing something like $11 billion when the budget was $6 billion)
  • 1 questionable detention facility in Cuba
  • And lots more stuff that doesn’t do me any good…

Wow, with that list, I should be more than happy to give the government more money! I reluctantly pay my taxes because if I didn’t, they’d nail me faster than Al Capone.

Error Message of the Day

I have to admit that some of my error messages to users aren’t that friendly, but I’ve tried to put in a message at least that says “Please contact support and report the above error.”. Sometimes I can help users, sometimes I can’t. I was using QuickBooks today and I received the following error message:

QuickBooksScreenSnapz001.jpg

That’s a huge help! It doesn’t tell me what to do, what happened, or how to correct it. Personally I think it looks completely unprofessional to display build paths in error messages. While they might help Intuit fix the issue, I don’t even have a clue where to start reporting that. Do I contact support@intuit.com or do I have to go through their web based system? I don’t want an answer, I just want to report it.

24/7 Tech Support

I received an urgent email from a user today that had a slight problem with a registration code. I responded within a couple of hours (today is Saturday) saying that I tested the code and it works fine (I actually did test the code and saw that it worked) and said please try again and if you have problems, please send a screenshot so I can verify the code. The response I got back was “I am very disappointed with your response.” Wow, it is those kind of users that make me want to quit writing and selling software. I generally get back to users within a few hours, 7 days a week; yes, I work a lot and some people appreciate that. Some support tickets take a bit of going back and forth and I’ll work to solve problems as much as it takes. However, getting responses like this make me want to just pack it in.

I generally expect an answer within a few days to any email I sent. (I have, however, been waiting 2 months for a response from IRIS about returning my crappy scanner, but that’s another store and I should just send them a certified letter and demand my money back as the product was falsely represented.) On weekends, I don’t expect any response as normal companies don’t have people working on weekends.

Oh, and ReceiptWallet 2.0 resets the 3 week demo period, so even if I didn’t get back quickly, all users would still have time to use the app before the demo expired.

Uggh.

Background Apps and the iPhone SDK

Yesterday I wrote that I didn’t think background applications were a good idea on the iPhone and I’ve seen some other posts that support my position; anything that makes the phone less stable is bad. A good friend of mine pointed out why a background app would be good, but also reminded me of how this was handled on the Palm OS. Notifications were posted. So an application would register for a notification or set an alarm for a specific time and then would handle that. While I’m not saying the Palm OS was perfect (if you ever wrote Palm OS software, you remember that notifications and alarm callbacks had to reside in the first 16K (or was it 64K) of the application thereby causing you to use jumps to get stuff to work. If you don’t have a clue what I’m talking about, feel lucky.

The iPhone could do something similar (maybe it already does, but I don’t know and even if I did, I couldn’t say publicly until June). I’m sure that the iPhone SDK will mature as time goes on, but I hope that developers remember that the iPhone is a phone and music player first, and an application player second (or third or fourth). If anything takes away from its main purpose, it will hurt the platform.