ThinkGeo.com    |     Documentation    |     Premium Support

Caching Queries

Hi,


I'm looking for some advice on the best way to set up caching for a WMS plugin. Quick bit of background:



        
  • We're dynamically filtering the layers by overriding the GetMapCore() method, as suggested in one of the examples.

  •     
  • We currently use Map Suite Web Edition to render MultiShapeFileFeatureLayers, but plan to move this rendering log out into the WMS server (still using Map Suite Web Edition on the client but using a WmsOverlay)


I've worked out that we can create a FileBitmapTileCache object in the GetMapCore() and assign that to the custom MapConfiguration object that contains the filtered layers. I'm setting the CacheId for the cache based on the filtered set of layers to ensure that a separate cache is maintained.


So my first question is what is the recommended way to set the CacheDirectory property. The plugin is obviously loaded dynamically so I'm not sure of the best way to pass this value in, and I don't want to hardcode a specific directory.


My second question is in regards to the FileBitmapTileCache class. I'm setting the CacheDirectory, CacheId and ImageFormat properties, but I'm not sure if I should be doing anything with the other properties, e.g. TileAccessMode, TileMatrix.


My third question concerns client-side caching. There doesn't seem to be a ClientCache property on a WmsOverlay in WebEdition though, so is client-side caching something that can/should be controlled through the WmsPlugin?


Finally, although I have the caching working at the moment with a hardcoded directory, the IIS worker process keeps on crashing with unhandled exceptions similar to the following:


The process cannot access the file 'C:\Temp\MapCache\4613998.359375\3183\3197.png' because it is being used by another process.


This is with a single user just panning and zooming around the map. The server is IIS 7.5 on Windows 7 64-bit.


Thank in advance for your help.


Best regards,


Gary



 Hi,


Sorry, I meant to add in the stack trace for the exception:



StackTrace:    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)


   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath)

   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)

   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)

   at System.IO.File.WriteAllBytes(String path, Byte[] bytes)

   at ThinkGeo.MapSuite.Core.FileBitmapTileCache.SaveTileCore(Tile tile)

   at ThinkGeo.MapSuite.Core.TileCache.SaveTiles(Bitmap bitmap, RectangleShape bitmapExtent)

   at sxM=.1BM=.DRQ=(Object DhQ=)

   at System.Threading.ExecutionContext.runTryCode(Object userData)

   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)

   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)

   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

   at System.Threading.ThreadHelper.ThreadStart(Object obj)


Best regards,


Gary




Gary,


 Here at the Support Team, we carefully read your questions and due to the conditions of your particular scenario, we are going to have to get some help from the Development Team. I am in communication with them for your case and we will get back to you soon. Thank you for your patience.



Hi Val, 
  
 Thanks for the update. 
  
 Gary

Gary,


  I talked to the Development Team about this post and this is what they suggested I respond:


1.    There is no “official” recommended way to set the CacheDirectory, we think that a good way potentially is to add the cache directory path to a configure file and put that file within the same directory of the plugins, so the plugins can get the cache directory on the fly when loaded.


2. Usually, you don't need to set the other properties which have good default values for the most common scenarios, of course you can change them if needed for your project. TileAccessMode determines the file access for you to Read/Add/Delete the cache, TileMatrix determines the scale of the cached images for example.


3. Thre is no ClientCache property on a WmsOverlay in WebEdition, the tiled images will go directly from the WMS server to the client browser instead of hitting the Web Server first. So, we can say that in this case there is only one "Client Cache" which is the Browser cache, and we cannot control it.


4. We wrote a simple sample to recreate the exception but failed (We tested on Win7 X64, IIS 7.5 with Map Suite 5.5.98.0). Could you send us a sample to us if this is possible?


Thank you.



Hi Val,


Thanks for your answers - they were very helpful.


Unfortunately I'm still getting the unhandled exception that kills the IIS worker process. I'm seeing this problem on a Windows Web Server 2008 R2 64-bit test server as well as the Windows 7 development machine.


I've tested this with the latest 5.5.111.0 version of MapSuite WMS.There are two errors logged in the Windows Event Log when the problem occurs:




 An unhandled exception occurred and the process was terminated.


Application ID: /LM/W3SVC/3/ROOT


Process ID: 3480


Exception: System.IO.IOException


Message: The process cannot access the file 'D:\MapCache\UK\288374.897460938\50854\51209.png' because it is being used by another process.


StackTrace:    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)

   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)

   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)

   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)

   at System.IO.File.WriteAllBytes(String path, Byte[] bytes)

   at ThinkGeo.MapSuite.Core.FileBitmapTileCache.SaveTileCore(Tile tile)

   at ThinkGeo.MapSuite.Core.TileCache.SaveTiles(Bitmap bitmap, RectangleShape bitmapExtent)

   at ohM=.0BM=.CBQ=(Object CRQ=)

   at System.Threading.ExecutionContext.runTryCode(Object userData)

   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)

   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

   at System.Threading.ThreadHelper.ThreadStart(Object obj)




Faulting application name: w3wp.exe, version: 7.5.7600.16385, time stamp: 0x4a5bd0eb

Faulting module name: KERNELBASE.dll, version: 6.1.7600.16850, time stamp: 0x4e211da1

Exception code: 0xe0434f4d

Fault offset: 0x000000000000a88d

Faulting process id: 0xd98

Faulting application start time: 0x01ccf87d9319487e

Faulting application path: c:\windows\system32\inetsrv\w3wp.exe

Faulting module path: C:\Windows\system32\KERNELBASE.dll

Report Id: 049094e6-6473-11e1-b0cb-0022196b2869




I only see the problem when using caching, which I'm doing with the following code inside my overriding GetMapCore() function:


            if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["EnableCaching"]) && ConfigurationManager.AppSettings["EnableCaching"].Equals("True", StringComparison.InvariantCultureIgnoreCase))
            {
               if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["CacheDirectory"]) && Directory.Exists(ConfigurationManager.AppSettings["CacheDirectory"]))
               {
                  FileBitmapTileCache tileCache = new FileBitmapTileCache();
                  tileCache.CacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"];
                  tileCache.CacheId = groups;
                  tileCache.ImageFormat = TileImageFormat.Png;
                  tileCache.TileAccessMode = TileAccessMode.ReadAdd;
                  customMapConfiguration.TileCache = tileCache;
               }
            }

As I mentioned, the problem seems to occur most often when rapidly panning around the map, e.g. quickly pan right and then left, or zoom out and immediately back in. My suspicion is that a subsequent request for a particular tile is coming in from the client before an earlier request has finished writing the file out to disk. This subsequent read isn't able to complete successfully because the writing process still has a lock on the file.


I assume the objects that write out the cache files are being Dispose()ed as soon as possible, but even if they are I would have thought this particular issue could easily be caught with an exception handler inside your ThinkGeo.MapSuite.Core.FileBitmapTileCache.SaveTileCore() function to prevent it from killing IIS.


Best regards,


Gary


P.S. I've ensured that there's no other processes running, e.g. Antivirus, that might be trying to access the file at the same time.



Gary,


 We tried to recreate your problem but without success. I sent you to your personal email the video on how we try to recreate the problem (the video is too big to be attached in this post). We are going to need more info from you. The best would be a simple sample which recreate the issue on your machine. we are waiting for your feedback. Thank you.



Hi Val,


Thanks - I got the video.


The problem is dififcult to replicate as it happens randomly. Sometimes it happens within a few minutes, sometimes it can be 15 minutes or longer before the process crashes.


I'll send you a video shortly directly to your personal e-mail address which I've just recorded now and shows the problem occuring on my development machine.


I believe it's almost certainly worse the faster the machine and the quicker you pan around, and I've managed to prove this to some degree by adding a call to sleep the thread inside the GetMapCore() override.


System.Threading.Thread.Sleep(1000)

I haven't seen the problem happen when this code is present, as I think all it's doing is giving the previous IIS thread chance to finish the write operation before the subsequent attempt to read/write the same file comes in.


Obviously this isn't an acceptable solution, and what's really needed is better file locking or some sort of semaphore to prevent the access occuring, to avoid a detrimental effect on performance.


Best regards,


Gary


 


 



Gary,


 Thank you for the video and the additional information you gave us. This will be discussed with the Development Team. As soon as we have something concrete on this, I will reply back.



Gary,


  We still have not been able to be recreated but we made a fix that might potentially also fix your issue. I will ask you to try the version 5.5.117.0 which will be available tomorrow. Please, let us know how this will help you. Thank you.



Hi Val,


Thanks for the update. I'll download this version tomorrow and let you know how my testing goes.


Best regards,


Gary



Hi Val,


I've downloaded 5.5.117.0 but unfortunately am still getting the same errors.


I noticed that the WmsServerEditionEvaluation5.5.117.0DllPacakge.zip file only contains the WmsServerEdition.dll files, and doesn't contain the MapSuiteCore.dll files that are present in other edition zip files. This seems strange as the exception is occuring inside the ThinkGeo.MapSuite.Core.FileBitmapTileCache.SaveTileCore() function so I would have expected to see updated versions of the core files if there was a fix for the problem.


I did download the 5.5.117.0 version of the Web Edition and put the 5.5.117.0 version of MapSuiteCore.dll that was included in that zip in my bin directory, but the problem still occurs.


I've been doing some further investigation to try and understand what the problem might be and have some ideas, but will send these to your private e-mail.


Best regards,


Gary



Gary,


  The Development team reported to me that a fix for the problem will be available tomorrow. Please, go to the Customer Portal tomorrow and try with the latest build. Let us know if you still have issues. Thank you.



Hi Val,


I've looked at the Customer Portal today and the WmsServerEditionEvaluation5.5.125.0.0DllPackage.zip is there with a build date of 3/15/2012. This zip file still only contains the WmsServerEdition.dll file though - I would have expected any fix to be in the MapSuiteCore.dll file as this is where the exception is occurring?


Is this a problem with the Daily Development Build zipping process for the WMS Server, as I've never seen any of the core files in the WmsServer daily build? If so, can I simply use the core files from one of the other evaluation products for the moment?


Best regards,


Gary



Gary,


  The fix was done to the core so you will need the new version of MapSuiteCore.dll. Please, use the MapSuiteCode.dll you can find in any of the other products. The fixes gets updated to all the products. I will advise the Development team to have the MapSuiteCore.dll included in the WMS server edition in the future. Thank you.



Hi Val,


I've download 5.5.126.0 and am sadly still getting the unhandled exceptions. This must mean that there still isn't any exception handling in place, otherwise the error wouldn't be getting propagated up the stack. I'm not sure what the fix was, but I can only assume that it was an attempt to avoid the lock occuring in the first place?


There is always going to be a possibility of a lock occurring though as these are happening down at the file system level, so even if you minimise the risk of this happening within Map Suite they could potentially be caused by other processes trying to access the file before the write has completed (e.g. virus programs, etc).


So rather than trying to achieve the impossible, I think it's better to look at how the situation is handled when a lock is encountered. The routine that attempts to write the cache files to disk has to be wrapped inside a try...catch block otherwise any unexpected errors will be propagated back up the stack and will ultimately cause the ASP.NET worker process to crash, just as we're seeing in our testing.


Obviously it would be ideal if the write always succeeded, but the impact of it failing is actually low, particularly if 99% of the time the tiles are written successfully (which is what we're seeing). As long as the bytes that represent the tile are streamed back to the client then it shouldn't cause any problems. If the file was locked by another process writing out the tile then the cached tile will just be served up on subsequent requests. If it wasn't written out for some reason then it just means that the the tile will be generated again on a subsequent request and hopefully cached successfully. Either way the client gets the bytes they need to render the image, and the ASP.NET worker process continues uninterrupted.


Best regards,


Gary


P.S. Thanks for sorting out the issue with the core DLLs not being present in the WMS Server Edition daily downloads - they were included in the .126 build.



Gary,


  I have not been involved in the fixing with the exception handling in the core. The Development team did. Before I pass all your precious comments to them, I would ask you to try with the Dev branch. Try with the version 5.5.0.126 (Dev branch) or higher. Let me know and if this is still the same, I will follow up with the Development Team. Thank you.



Hi Val,


I realise it's not you that's fixing the code - I'm providing as much information as I can so that you can pass it onto the development team.


I did use the Daily Development Build in the Evaluation Daily Builds section of the customer portal. This shows the version as 5.5.126.0.


On the portal the daily production builds seem to increment the last part of the build - the last Daily Production Build was 5.5.0.103.


Best regards,


Gary



Gary, 
  
   I hope I can shed some light on the locking versus catching strategy.  First let me say that we now handle the exceptions from the SaveTile method which gets called by the SaveTiles method and this will be available in the next daily build 5.5.0.130 or later but may be 131 as our builder runs during the day and about now so I am not sure.  
  
   In general we try not to catch and eat exceptions and prefer to throw them so they are visible and can be addressed.  One important reason is that catching exceptions really slows down the performance of the application in the mainstream advice is to have a main flow of the application that should not rely on error trapping.    What we should do is to have you send us in an example that perfectly re-creates the issue and then we can solve the issue without having blanket error trapping.  Of course we want to fix things fast and this was a hard one to recreate, in fact we have yet to do it.  Our first attempt was to lock the shared resource and I was fairly confident this would solve it however there may be other things in play besides just our code so it was not successful.  I have no problem doing the blanket error trap in this case, as you pointed out, failure is not a big deal and it helps make the system more stable against things like other issue as you mentioned. 
  
 David

Hi David,


Thank you for your comments. I completely agree with your opinion that it's better not to eat exceptions, but the problem in this instance was that there was no way for the exception to be caught as it occurs outside of any user-generated code.


I also understand your difficulty in replicating the problem - attempting to produce a fix for a problem you can't recreate isn't an easy task. The problem is unpredictable here too in our development and test environments. I ran the .126 DLL for several hours without any errors and was just about to update this forum topic with good news when half a dozen exceptions occurred within a few minutes.


From this evidence I think there is almost certainly an environmental element to it, whether that be physical factors like disk speeds, CPU cores, memory, etc. (client and server), user factors like the way I'm panning and zooming the map, or even data factors such as the complexity of certain tiles causing the rendering times to vary.


So even with a full copy of our code and data files I think you might still have difficulties in replicating the problem. If I comment out the tile caching code I included earlier in the topic the problem doesn't happen at all, but that's to be expected as there shouldn't be any attempt to write the image files out to disk.


In any case, thank you for including the exception handling as this will allow us to move forward. I'll post an update later in the week when we've tried it out.


Best regards,


Gary