# Buggy code
def MergeAttributeContainers(self, callback=None, maximum_number_of_containers=0):
    """Reads attribute containers from a task storage file into the writer.

    Args:
      callback (function[StorageWriter, AttributeContainer]): function to call
          after each attribute container is deserialized.
      maximum_number_of_containers (Optional[int]): maximum number of
          containers to merge, where 0 represent no limit.

    Returns:
      bool: True if the entire task storage file has been merged.

    Raises:
      RuntimeError: if the add method for the active attribute container
          type is missing.
      OSError: if the task storage file cannot be deleted.
      ValueError: if the maximum number of containers is a negative value.
    """
    if maximum_number_of_containers < 0:
        raise ValueError("Invalid maximum number of containers")

    if not self._cursor:
        self._Open()
        self._ReadStorageMetadata()
        self._container_types = self._GetContainerTypes()

    number_of_containers = 0
    while self._active_cursor or self._container_types:
        if not self._active_cursor:
            self._PrepareForNextContainerType()

        if maximum_number_of_containers == 0:
            rows = self._active_cursor.fetchall()
        else:
            number_of_rows = maximum_number_of_containers - number_of_containers
            rows = self._active_cursor.fetchmany(size=number_of_rows)

        if not rows:
            self._active_cursor = None
            continue

        for row in rows:
            identifier = identifiers.SQLTableIdentifier(
                self._active_container_type, row[0]
            )

            if self._compression_format == definitions.COMPRESSION_FORMAT_ZLIB:
                serialized_data = zlib.decompress(row[1])
            else:
                serialized_data = row[1]

            attribute_container = self._DeserializeAttributeContainer(
                self._active_container_type, serialized_data
            )
            attribute_container.SetIdentifier(identifier)

            if self._active_container_type == self._CONTAINER_TYPE_EVENT_TAG:
                event_identifier = identifiers.SQLTableIdentifier(
                    self._CONTAINER_TYPE_EVENT, attribute_container.event_row_identifier
                )
                attribute_container.SetEventIdentifier(event_identifier)

                del attribute_container.event_row_identifier

            if callback:
                callback(self._storage_writer, attribute_container)

            self._add_active_container_method(
                attribute_container, serialized_data=serialized_data
            )

            number_of_containers += 1

        if (
            maximum_number_of_containers != 0
            and number_of_containers >= maximum_number_of_containers
        ):
            return False

    self._Close()

    os.remove(self._path)

    return True


# Traceback (ValueError)
Traceback (most recent call last):
File "plaso/tools/log2timeline.py", line 97, in <module>
if not Main():
File "plaso/tools/log2timeline.py", line 72, in Main
tool.ExtractEventsFromSources()
File "plaso/plaso/cli/log2timeline_tool.py", line 448, in ExtractEventsFromSources
worker_memory_limit=self._worker_memory_limit)
File "plaso/plaso/multi_processing/task_engine.py", line 812, in ProcessSources
self._ProcessSources(source_path_specs, storage_writer)
File "plaso/plaso/multi_processing/task_engine.py", line 343, in _ProcessSources
self._ScheduleTasks(storage_writer)
File "plaso/plaso/multi_processing/task_engine.py", line 449, in _ScheduleTasks
self._MergeTaskStorage(storage_writer)
File "plaso/plaso/multi_processing/task_engine.py", line 256, in _MergeTaskStorage
maximum_number_of_containers=self._MAXIMUM_NUMBER_OF_CONTAINERS)
File "plaso/plaso/storage/sqlite/merge_reader.py", line 278, in MergeAttributeContainers
self._active_container_type, serialized_data)
File "plaso/plaso/storage/interface.py", line 600, in _DeserializeAttributeContainer
attribute_container = self._serializer.ReadSerialized(serialized_string)
File "plaso/plaso/serializer/json_serializer.py", line 404, in ReadSerialized
return cls.ReadSerializedDict(json_dict)
File "plaso/plaso/serializer/json_serializer.py", line 423, in ReadSerializedDict
json_object = cls._ConvertDictToObject(json_dict)
File "plaso/plaso/serializer/json_serializer.py", line 252, in _ConvertDictToObject
'supported.').format(attribute_name))
ValueError: Event data attribute value: return_value of type dict is not supported.

# Fixed code
def MergeAttributeContainers(self, callback=None, maximum_number_of_containers=0):
    """Reads attribute containers from a task storage file into the writer.

    Args:
      callback (function[StorageWriter, AttributeContainer]): function to call
          after each attribute container is deserialized.
      maximum_number_of_containers (Optional[int]): maximum number of
          containers to merge, where 0 represent no limit.

    Returns:
      bool: True if the entire task storage file has been merged.

    Raises:
      RuntimeError: if the add method for the active attribute container
          type is missing.
      OSError: if the task storage file cannot be deleted.
      ValueError: if the maximum number of containers is a negative value.
    """
    if maximum_number_of_containers < 0:
        raise ValueError("Invalid maximum number of containers")

    if not self._cursor:
        self._Open()
        self._ReadStorageMetadata()
        self._container_types = self._GetContainerTypes()

    self._deserialization_errors = []

    number_of_containers = 0
    while self._active_cursor or self._container_types:
        if not self._active_cursor:
            self._PrepareForNextContainerType()

        if maximum_number_of_containers == 0:
            rows = self._active_cursor.fetchall()
        else:
            number_of_rows = maximum_number_of_containers - number_of_containers
            rows = self._active_cursor.fetchmany(size=number_of_rows)

        if not rows:
            self._active_cursor = None
            continue

        for row in rows:
            identifier = identifiers.SQLTableIdentifier(
                self._active_container_type, row[0]
            )

            if self._compression_format == definitions.COMPRESSION_FORMAT_ZLIB:
                serialized_data = zlib.decompress(row[1])
            else:
                serialized_data = row[1]

            try:
                attribute_container = self._DeserializeAttributeContainer(
                    self._active_container_type, serialized_data
                )
            except IOError as exception:
                # TODO: store this as an extraction warning so this is preserved
                # in the storage file.
                logger.error(
                    (
                        "Unable to deserialize attribute container with error: {0!s}"
                    ).format(exception)
                )

                identifier = identifier.CopyToString()
                self._deserialization_errors.append(identifier)
                continue

            attribute_container.SetIdentifier(identifier)

            if self._active_container_type == self._CONTAINER_TYPE_EVENT_TAG:
                event_identifier = identifiers.SQLTableIdentifier(
                    self._CONTAINER_TYPE_EVENT, attribute_container.event_row_identifier
                )
                attribute_container.SetEventIdentifier(event_identifier)

                del attribute_container.event_row_identifier

            if callback:
                callback(self._storage_writer, attribute_container)

            self._add_active_container_method(
                attribute_container, serialized_data=serialized_data
            )

            number_of_containers += 1

        if (
            maximum_number_of_containers != 0
            and number_of_containers >= maximum_number_of_containers
        ):
            return False

    self._Close()

    os.remove(self._path)

    return True