Search code examples
c#linqwcfindexoutofboundsexceptiongeneric-list

In generic list, List.Last() throws System.IndexOutOfRangeException even after check of Count


I have following code at my WCF service, which is throwing System.IndexOutOfRangeException

Dictionary<string, List<ChunkData>> chunkDataTable = cacheManager[cacheKey] as Dictionary<string, List<ChunkData>>;
  if (!chunkDataTable.Keys.Contains(ticker))
  {
      chunkDataTable.Add(ticker, new List<ChunkData>());
  }
  List<ChunkData> listOfChunks = new List<ChunkData>();
  ChunkData lastIncompeleteChunk = null;
  if (chunkDataTable[ticker].Count > 0)
  {
      Logger.Write("Log one");  //I see this logs
  if (reqEndDate <= chunkDataTable[ticker].Last().EndDate)
  {
      //meaning we can directly take last chunk (which can be incompelete too) directly as we dont need to add further data into for now
      listOfChunks = chunkDataTable[ticker].Where
          (chunk =>
            ((chunk.StartDate >= reqStartDate && chunk.EndDate <= reqEndDate) ||      //whole chunks: which lies in between of requested start and end date
            (chunk.StartDate <= reqStartDate && reqStartDate <= chunk.EndDate) ||     //Left Most Chunk: if req start date lies within some chunk (between start and end date of chunk)
            (chunk.StartDate <= reqEndDate && reqEndDate <= chunk.EndDate)            //Right Most Chunk: if req end date lies within some chunk (between start and end date of chunk)
            )).OrderBy(x => x.EndDate).ToList();
  }
  else
  {
      listOfChunks = chunkDataTable[ticker].Where
        (chunk => !chunk.IsIncomplete &&
            ((chunk.StartDate >= reqStartDate && chunk.EndDate <= reqEndDate) ||      //whole chunks: which lies in between of requested start and end date
            (chunk.StartDate <= reqStartDate && reqStartDate <= chunk.EndDate) ||     //Left Most Chunk: if req start date lies within some chunk (between start and end date of chunk)
            (chunk.StartDate <= reqEndDate && reqEndDate <= chunk.EndDate)            //Right Most Chunk: if req end date lies within some chunk (between start and end date of chunk)
            )).OrderBy(x => x.EndDate).ToList();
      if (chunkDataTable[ticker].Last().IsIncomplete)
          lastIncompeleteChunk = chunkDataTable[ticker].Last();
  }

  if (listOfChunks != null)
      Logger.Write("Line two"); //I don't see this log

}

here reqEndDate and reqStartDate are coming form client computer and as per logs they are correct. cacheManager is Microsoft enterprise library's cache manager

Full stack trace:

System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at System.Linq.Enumerable.Last[TSource](IEnumerable`1 source)

I wonder, at what point such exception possibly occuers


Solution

  • Stack trace suggests that this is a threaing issue. Last() is implemented something like this:

    IList<TSource> sourceList = source as IList<TSource>;
    if (sourceList != null)
    {
        int count = sourceList.Count;
        if (count > 0)
          return sourceList[count - 1];
    }
    

    So if IEnumerable you pass implements IList (like in your case), it will first save total length in a variable and then will access last element. The only way exception with your stack trace can be thrown is if number of elements in source was changed between sourceList.Count and sourceList[count - 1] statements, which can only be done by another thread.

    If you are using shared cache - that change can be made by another WCF thread. So to fix - implement proper synchronization when working with items from your shared cache.