Every developer knows that kind of situations, where you spend hours chasing a problem, that suddenly appeared, and the more you debug and dive into fixing it, the more you get confused and unnerved. And when you finally have found the solution, it turns out, that it was a very simple and stupid bug, you've created unwittingly - the sort of cause, you haven't ever thought about, and you feel like bumping your head hard onto the wall...
Well, exactly such a situation happened to me yesterday, when I was working on a Feed importer for a customer - and I think, that one can get into exactly the same situation very easily. And I'll bet, many others would also get easily misleaded. So I want to share my experience with the world, so that others may prevent a similar situation :-)
The ingredients of this mean problem
Under certain circumstances the most easiest-to-fix problem can disguise itself and bring you a lot of troubles. In my situation, these factors together resulted in a "toxic cocktail":
- a tiny change, that I've tested successfully
- a even more tiny change to the first one AFTER testing (change a function name)
- a failed FTP transfer (which I did not recognize)
- another, little bit bigger change (which was innocent)
- running the importer via AJAX (Batch API) - thus not seeing the full page reload
- seeing strange results that seems to be related to the bigger change
My Feeds importer was already in use and tested. I only had to do a very small configuration change, and a very small improvement in my hook_feeds_presave implementation, and - in order to improve the performance signficantly - implement hook_feeds_after_parse and do some filtering of duplicate entries (that would not have been possible in that form with a Feeds Tamper plugin).
First, I have changed the configuration, then wrote the small change in the presave hook, afterwards tested it successfully, and then decided to move forward to the next problem. But before I actually did, I've just changed a function name in a helper class because I felt that name would be semantically more correct and better. I've did the change to both affected files. And there was really no need to test it again... So I started to implement my filter in the hooks_feeds_after_parse hook.
But there was one thing I have missed after changing my function name earlier: the FTP transfer of the helper class failed, so that an ordinary "Fatal error: Call to undefined method" will be caused when running the importer the next time.
You may ask, how I could not recognize that...
Well, the first reason is, that a fatal error was raised, and not an execption. Exceptions would have been caught and logged by Feeds Processor. The error just ends the request abruptly. And as the Batch API didn't get the response, that the job has finished, it just calls it another time. If you do not abort the import run forcefully, it would run infinitely!
But in my case, the problem was even more mean. As I mentioned already, I've implemented a duplicate filter, whose cache is emptied after finishing an import run successfully. So it happened, that every chunk of the CSV file was processed exactly two times: the first time, the fatal error happened, but the second time my filter already marked the unique key column as processed, so that it excluded the row from the result set. So, there was no infinite run, but on the other side, none of my items got updated.
For sure, I've blamed the filter and/or Feeds for causing the problem. First, I was thinking of a concurrent execution problem. I was already debugging deeply in various Feeds functions, when I saw, that after skipping saving the entity and also my custom processing in the presave hook, the filter worked correctly, and no chunk was processed twice. But I was already on the wrong trail and still needed some time to find out, what was really causing the problem.
Lessons learned
- If your Feeds importer is running endlessly, or causing some other strange behaviour like processing the same items multiple times, you should first check your browser's console and the responses of your requests!!
- I sometimes really hate PHP for not being a strongly typed language such as Java (as well as being interpreted and not compiled...)
- even the tiniest and simpliest changes can have a huge impact and cause big problems...
Asynchronous JavaScript and XML (AJAX) bezeichnet eine Webentwicklungstechnik, bei der JavaScript Daten asynchron vom Browser an einen Server sendet und Antworten empfängt, ohne die gesamte Webseite neu zu laden. Dadurch lassen sich Inhalte dynamisch aktualisieren und interaktive Benutzeroberflächen realisieren, etwa bei Suchfeldern oder Formularen. Obwohl der Begriff XML nennt, werden heute häufig JSON-Daten verwendet; AJAX bildet zudem eine Grundlage moderner Webanwendungen und API-Integrationen.
Eine Application Programming Interface ist eine standardisierte Programmierschnittstelle, über die Softwareanwendungen Daten und Funktionen kontrolliert miteinander austauschen können. Sie legt fest, welche Anfragen ein System akzeptiert, in welchem Format Informationen übermittelt werden und welche Antworten zurückgegeben werden. In der Webentwicklung ermöglicht sie beispielsweise die Anbindung von ERP-, CRM- oder KI-Systemen an eine Drupal-Plattform und bildet damit eine wichtige Grundlage für skalierbare, integrierte digitale Lösungen.
Eine Application Programming Interface ist eine standardisierte Programmierschnittstelle, über die Softwareanwendungen Daten und Funktionen kontrolliert miteinander austauschen können. Sie legt fest, welche Anfragen ein System akzeptiert, in welchem Format Informationen übermittelt werden und welche Antworten zurückgegeben werden. In der Webentwicklung ermöglicht sie beispielsweise die Anbindung von ERP-, CRM- oder KI-Systemen an eine Drupal-Plattform und bildet damit eine wichtige Grundlage für skalierbare, integrierte digitale Lösungen.
Eine Entity ist ein eindeutig identifizierbares, strukturiertes Datenobjekt innerhalb eines Softwaresystems. In Drupal werden beispielsweise Inhalte, Benutzerkonten, Medien und Taxonomiebegriffe als Entities verwaltet; sie besitzen eine eigene ID, definierte Felder und können über die Entity API bearbeitet sowie mit weiteren Systemen verknüpft werden. Dadurch lassen sich flexible und skalierbare Datenmodelle für Websites, Portale und Enterprise-Anwendungen umsetzen.
Java ist eine objektorientierte, plattformunabhängige Programmiersprache und Laufzeitumgebung für die Entwicklung von Webanwendungen, Unternehmenssoftware und mobilen Anwendungen. Java-Programme werden in Bytecode übersetzt und auf der Java Virtual Machine (JVM) ausgeführt, wodurch sie auf unterschiedlichen Betriebssystemen lauffähig sind. In digitalen Projekten kommt Java beispielsweise für skalierbare Backend-Systeme, Microservices und Schnittstellen zu CMS-, ERP- oder KI-Anwendungen zum Einsatz.
PHP ist eine serverseitig ausgeführte Programmiersprache, mit der dynamische Websites, Webanwendungen und Schnittstellen entwickelt werden. Sie bildet unter anderem die technische Grundlage von Drupal und wird im Enterprise-Umfeld häufig gemeinsam mit dem Symfony Framework eingesetzt, um skalierbare, wartbare und sichere Backend-Systeme zu realisieren. Der PHP-Code wird auf dem Webserver verarbeitet und erzeugt daraus beispielsweise HTML oder strukturierte API-Antworten.