tags:

views:

123

answers:

7

I'm working with two independent c/c++ applications on Windows where one of them constantly updates an image on disk (from a webcam) and the other reads that image for processing. This works fine and dandy 99.99% of the time, but every once in a while the reader app is in the middle of reading the image when the writer deletes it to refresh it with a new one.

The obvious solution to me seems to be to have the reader put some sort of a lock on the file so that the writer can see that it can't delete it and thus spin-lock on it until it can delete and update. Is there anyway to do this? Or is there another simple design pattern I can use to get the same sort of constant image refreshing between two programs?

Thanks,

-Robert

+3  A: 

Yes, a locking mechanism would help. There are, unfortunately, several to choose from. Linux/Unix e.g. has flock (2), Windows has a similar (but different) mechanism.

Another (somewhat hacky) solution is to just write the file under a temporary name, then rename it. Many filesystems guarantee that a rename is atomic, so this may work. This however depends on the fs, so it's a bit hacky.

sleske
I actually am using a rename technique right now, but I don't see how that will fix it for all cases. For instance, let's say the reader is halfway through reading the file, but the writer is ready to update it (it first has written to the temporary file and is about to rename it to the filename the reader expects). When it renames it, and thus destroys the first file, the reader, which is only halfway through reading the first file, will surely get an error as the file it is reading is clobbered, right?
JoeCool
Don't know about Windows, but in Unix/Linux, what will happen is that the rename will unlink the previous version of the file, but as long as the reader has an open file descriptor, it can keep reading happily. The rename trick is a standard mechanism for making sure that the reader gets either the old or new version of the file, and not half-and-half.
David Gelhar
Ah, that sounds perfect. But when you say rename, do you mean to first delete the original with a separate statement, or does the rename automatically (and atomically?) delete the original before the rename?
JoeCool
@Joe, the reader can in turn rename just before opening the file for reading, effectively taking ownership. The file name would change from `writer_private_name` -> `public_name` -> `reader_private_name`.
vladr
+2  A: 

Or, as a really simple kludge, the reader creates a temp file (says, .lock) before starting reading and deletes it afterwards. The write doesn't manipulate the file so long as .lock exists.

That's how Open Office does it (and others) and it's probably the simplest to implement, no matter which platform.

Mawg
But also results in horrible horrible race conditions.
Billy ONeal
I tried this exact method and indeed also got the included race conditions. Without an atomic operation (on the writer's end) that both checks to see if there is a .lock file and then refreshes the image if there isn't, this won't fix the problem.
JoeCool
vladr
sleske
+1 for `O_CREAT|O_EXCL`
Mawg
@sleske, it's OK to detect whether the lock is stale (e.g. via PID checking); the complication arises when and if multiple processes *concurrently* arrive at the conclusion that the lock is stale and must acquire a second lock before removing the first lock (otherwise proc A detects stale lock, proc B detecst stale lock, proc A remove stale lock, proc A acquires new lock, proc B removes proc A's lock thinking it's still the stale lock) which introduces the problem of that second lock becoming stale etc. ad infinitum. :)
vladr
@Vlad: Ah, I see, interesting. Amazing how complicated concurrency problems can become...
sleske
+3  A: 

If you are willing to go with the Windows API, opening the file with CreateFile and passing in 0 for the dwShareMode will not allow any other application to open the file.

From the documentation:

Prevents other processes from opening a file or device if they request delete, read, or write access.

Then you'd have to use ReadFile, WriteFile, CloseFile, etc rather than the C standard library functions.

shf301
A: 

Instead of deleting images, what about appending them to the end of the file? This would allow you to keep adding to the file while the reader is still operating without destroying the file. The reader can then delete the image when it's done with it (provided it is necessary) and move onto the next image. Or, the other option would be store the image in a buffer, for writing, and you test the file pointer. If it's set to the head of the file then you can go ahead and write from the buffer to the file. Otherwise, wait until reader finishes and puts the pointer back at the head of the file.

+4  A: 

Try using a synchronization object, probably a mutex will do. Whenever a process wants to read or write to a file it should first acquire the mutex lock.

Ramakrishna
But that only works in a multi-threaded context. I'm asking about what to do when two applications, that have no knowledge of each other besides the image file, need to share the resource.
JoeCool
Create a named mutex and that can be used by both processes.
Ramakrishna
Ah, that sounds like perhaps the best option. I will give that a shot, thanks!
JoeCool
@Joe, not necessarily. Unless you have specific timing needs, the mutex approach is reinventing the wheel and you should probably be using filesystem locking instead.
vladr
A: 

couldn't you store a few images? ('n' sounds like a good number :-)

Not too many to fill your disk, but surely 3 would be enough? if not, you are writing faster than you can process and have a fundamental problem anyhoo (tune to discover 'n').

Cyclically overwrite.

Mawg
A: 

Joe, many solutions have been proposed; I commented on some of them but I'd like to chime in with an overall view and some specifics and recommendations:

You have the following options:

  1. use filesystem locking: under Windows have both the reader and writer open (and create with the CREATE_ALWAYS disposition, respectively) the shared file in OF_SHARE_EXCLUSIVE mode; have both the reader and writer ready to handle ERROR_SHARING_VIOLATION and retry after some predefined period of time (e.g. 250ms)
  2. use file renaming to essentially transfer file ownership: have the writer create a writer-private file (e.g. shared_file.tmpwrite), write to it, close it, then make it publicly available to the reader by renaming it to an agreed-upon "public" name (e.g. simply shared-file); have the reader periodically test for the existence of a file with the agreed-upon "public" name (e.g. shared-file) and, when one is found, attempt to first rename it to a reader-private name (e.g. shared_file.tmpread) before having the reader open it (under the reader-private name); under Windows use MOVEFILE_REPLACE_EXISTING; the rename operation does not have to be atomic for this to work
  3. use other forms of interprocess communication (IPC): under Windows you can create a named mutex, and have both the reader and writer attempt to create (the existing mutex will be returned if it already exists) then acquire the named mutex before opening the shared file for reading or writing
  4. implement your own filesystem-backed locking: take advantage of open(O_CREAT|O_EXCL) or, under Windows, of the CREATE_NEW disposition to atomically create an application lock file; unlike OF_SHARE_EXCLUSIVE approach above, it would be up to you to deal with stale lock files (i.e. lock files left by a process which did not shut down gracefully such as after a crash.)

I would implement method 1.

Method 2 would also work, but it is in a sense reinventing the wheel.

Method 3 arguably has the advantage of allowing your reader process to wait on the writer process and vice-versa, eliminating the need for the arbitrary sleep delays between the retries of methods 1 and 2 (polling); however, if you are OK with polling then you should still use method 1

Method 4 is listed for completeness only, as it is complex to implement (when the lock file is detected to be stale, e.g. by checking whether the PID contained therein still exists, multiple processes can potentially be competing for its removal, which introduces a race condition requiring a second lock, which in turn can become stale etc. etc., e.g.:

  • process A creates the lock file but dies without removing the lock file
  • process A restarts and tries to acquire the lock file but realizes it is stale
  • process B comes out of a sleep delay and also tries to acquire the lock file but realizes it is stale
  • process A removes the lock file, which it knew to be stale, and recreates it essentially reacquiring the lock
  • process B removes the lock file, which it (still) thinks is stale (although at this point it is no longer stale and owned by process A) -- violation
vladr