This article is written in English and Portuguese (original is here)
Este artigo está escrito em Inglês e Português (o origianl está aqui)
English version:
Introduction
Back to a real life situation, this time with 4GL. It has some technical details that are interesting, and it's also a good example of how to debug and solve a problem (modesty apart, but as you'll see the merit is not even mine - several people contributed to this - ). It's also a perfect example of how technical support tends to work these days, and as I usually say, customers receive what they demand (less quality and less availability to help). Again, IBM technical support stood up from other companies. And yes, I'm biased, but along the article I'll show you the differences and you can judge for yourself.
I'm presenting this as a warning to any customer wanting to do something similar (which is not supported, so should be avoided) and as a possible workaround if you already did it, and face the same issues.
I will not mention other vendor's names,as I will criticize their support, and for that I will have to blur some details. Hopefully it will not prevent the understanding of the problem.
Problem description
4GL is a programming language which we can integrate with other components through the usage of C language functions. 4GL is not supposed to be thread safe. Threads can be seen as parallel or concurrent programming aids. They can more or less act as a process, but are lighter and because of that they're used in many situations. However, because a process can run several threads, they all share common address space and the operating systems don't isolate them as they do with processes (in fact, if they did, threads would loose several of their advantages). This can have bad consequences. We must be very carefully when working with a threaded programming model, because one thread can overwrite data used by other threads and this can lead to all kinds of problems. There are several techniques to achieve thread safety like code re-entrance, mutual exclusion, using local instead of global variables etc. For the record, a thread has a private stack and the context of the thread and some local variables can be stored there. So, each thread's stack should be "sacred" ground, not to be invaded by others.
Saying 4GL is not thread safe means that the internal 4GL code was not implemented with the above concepts in mind. It depends on global variables for example. But it can also do "nasty" things that affect other threads on the same process, even when those threads are not running 4GL functions and code.
In my situation, a customer linked a 4GL application with a third-party library (middleware software) that internally uses threads (it creates one thread to be exact). That library allows the 4GL application to extract/request data from other systems. It's important to note that this was done years ago (5-10 years) and it's been working without anybody noticing any issue. At some point, due to a system overload, customer decided to move a group of users to another system, running the same versions of OS, 4GL and third party integration software. Initially a few small differences between the systems were spotted, but after being eliminated the problem still persisted. On the "new" systems (in fact they're relatively old systems, running out of regular support), users started to experience a lot of application crashes. A core dump was generated and it was reported to me for analysis.
I've been working with 4GL since 1991 (wow... 22 years!), first as a programmer and after I joined Informix (1998) as a consultant, so I'd say I have a large experience with the language (although nowadays I honestly feel a bit rusty because I don't do 4GL every day). But what I'm trying to say is that it's obviously not the first time I see a 4GL core. Typically they're caused by exceeding the array limits or because a C function with some problem was linked with the main process, or very rarely by a bug. So I usually start with a debugger (dbx for example) to extract the core's stack trace. It usually tells us where the problem happened, and we can then do some code analysis. Another option is to recompile all the modules with "-a" flag of c4gl command. It includes some array protection code that will force the program to stop if an invalid array position is used. This can slow the program, but it's a pretty good way to catch array dimensioning issues.
In this particular case, the stack trace was nearly useless because it looked "corrupted", and it only showed operating system functions related to exception handling. Meaning the OS perceived something was wrong, but while trying to generate the core it got confused. The -a option was used without success.. The crashes seem to be relatively random, in different parts of the code or user options.
A more empirical approach was followed. We picked a program that crashed frequently in some option and tried to strip it out of as many things as we could. We ended up with a much more simple test case that we could use to reproduce the crash. After a few repetitions of doing the same thing we usually got the core. We understood that the crash typically happened around reading a key. The first suspect we had was 4GL... There is a documented potential issue when some input structures (INPUT, PROMPT, MENU etc.) are used inside a loop and we exit the loop without EXITing the input structure properly. Mainly because of this (the test code resembled a brief description of the issue), we opened a PMR with IBM. That was the first time we got in touch with any of the vendors involved (IBM for 4GL, one for the OS and another for the third party library). After a bit of investigation we cleared this possibility. IBM technical support was helpful in understanding some details we gathered with a debugger. But for a while we were a bit stuck. I must explain that by then, there were three people deeply involved in this situation: I was trying a more "practical" approach to isolate the problem by changing and trying to simplify the test case; A colleague from IBM technical support who was trying to check similar issues already reported, and helping with our doubts, as he is more knowledgeable in debugging techniques; A customer team member who was trying the "scientific" approach. He spent hours running the test case on a debugger and he even looked at the assembler (and believe me, you'll never be the same after trying to understand the assembler of this old, poorly documented and complex platform).
I managed to confirm that if the application didn't call the third party function it wouldn't happen. Taking a closer look at the code that glue 4GL with the third party system I've found several problems, but fixing them didn't help. And I was getting stuck. But the customer noticed that a certain behavior in the thread status seemed to happen just before the crash. He was almost sure that the threads and/or thread management had something to do with it. With this findings in mind we noticed that on the core dump, the functions from the third party library seemed to vanish from the status of the process after the crash. So I changed the 4GL investigations from user input structures and the like to issues around threads. Needless to say neither me or my colleague could find too many situations, because 4GL is not thread safe... so people usually don't use threads. But we've been able to find a few reports:
- One on the same platform as our situation. But the customer had the option to link with a non threaded library, so the problem was solved without further investigation
- A very similar problem, related to keyboard reading, on AIX. Some OS parameters controlling thread scheduling were changed and the problem disappeared
- A not very well documented issue on Solaris where some workaround was implemented by IBM, but not included in the mainstream code.
It's important to recall that we had two systems where this happened and several where we could not reproduce it. The systems looked similar and the customer system administrators couldn't find any reason why the application crashed on some, but not on most of their systems. After some insistence they agreed to open a case on their operating system supplier. I explained we were looking for help in debugging the issue and that we had no indication that the cause was in the operating system.
A sort of conference call with the possibility of desktop sharing was scheduled and I showed the problem happening. During the core some cryptic message was shown... as soon as the engineer saw the message (something like "unable to unwind stack") she wrote: "That's an application issue!"... Well... Yes.. In theory I could agree with that. But it didn't help us. It didn't explain why it only happened in this system. It also didn't explain why the OS couldn't generate a "clean" core that was helpful for us... After a bit of talk, and after I insisted that what we were looking for was some more deep knowledge on the OS debugging tools and outputs, another engineer joined the call. He was much more willing to help, although at the time no magic was done...
They agreed about investigating some points I raised regarding OS behavior and thread signal handling (referenced in other IBM PMRs), and we proceed with IBM investigation and test case elaboration.
At this point the status was:
1- We were almost sure it had to do with threads (thanks to the customer team member) that allowed us to find a useful old PMR
2- We were puzzled by the fact that the same code behaved differently on different systems and asked OS supplier to help us with that
3- Since 4GL was not meant to work in a multi-thread environment we tried to verify if there was some configuration related to the third-party library that would allow us to avoid thread creation
The OS support got back to us with some more vague details, but nothing that would allow us to change thread scheduling behavior (like it was done for AIX). They couldn't also explain why it happened on some systems and not on others.
Meanwhile we got interested in the way 4GL deals with signal handling. We started to suspect that 4GL was setting up some signal handlers and that one (or more) was being executed in the context of the thread created by the library.
Given 3) above I suggested that we get more involvement of the customer team managing the third party integration tool. But because they were not able to give us any answers, we decided to open a case with their supplier's support. The questions were:
1- Is there a way to avoid the thread creation?
2- If not, is there any way to control the time it stays "idle" (we noticed the thread was mostly idle, but it was waking at regular intervals)
3- Was there any way to control the signals it would respond to?
There was just one answer, and it was staggering simple: "Sorry Mr. customer. You're using an out of support version. Please upgrade before asking any questions"
Don't get me wrong... I do understand and even sympathize with lack of support for very old software versions. But we were not asking for any code debuging, or code change. And the questions would probably be valid if it was a supported version (as per their manual, the same function interfaces were still in use in recent versions). So this answer was a bit strong. I've seen much worse situations being answered by IBM Informix support. But customer accepted it, so I couldn't do anything about it.
Basically we were a bit stuck... not much help from other tech supports.. Our technical support suggested that when reading the keyboard, 4GL was doing something to handle the special key "ESCAPE" that could cause troubles.... Very shortly, the "escape" code can mean the escape key or the beginning of an escape sequence (used in terminal emulators). So it does a "read()" and gets the escape code. After that it has a dilemma: If it's an escape sequence (starts with the escape code) the program has to read() again. But if it was just the escape key the call to read() will block. 4GL solves this by calling "alarm()" and then read() again. The alarm() call will trigger a signal (ALRM) to be sent to the process after the specified time. If by then the read() is still active, the alarm signal handler (setup by 4GL) will cancel the call and proceed. This is done by manipulating the current stack. It's a dirty trick, but it is, or at least was common a few years ago. We were able to find
discussions about Apache issues because it uses the same concept (naturally not for keyboard reading).
With this in mind we decided to try a workaround by changing the 4GL internal code. I was impressed by technical support ability and willingness to try this. The mechanism above was changed for another method that avoided the second call to read() (blocking). A non blocking method was used and they created a patch (non-official). This was put into place and the program stopped crashing on that specific point. That was the good news. The bad was that it kept crashing on other places, and assuming it was caused by the signal handling outside of the 4GL thread context, it would not be possible to change it without major changes in the 4GL internal code. And it was clear that it would be very difficult to implement those, and R&D would not see that as a real possibility.
The solution
That was when a more pragmatic approach was taken. System documentation tells us that a thread B created by thread A will inherit the "signal mask" of A. The signal mask is a bit array where each bit matches a specific signal and if it's on/off the signal will/won't be received by the thread. As we don't want the third party thread to receive the signals for which 4GL sets up handlers, we can change the signal mask just before creating that thread, and restoring the initial one just after the thread creation.
IBM technical support agreed this was worth trying and provided a list of possible signals to mask.
Technically, doing this is terribly simple:
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
[…]
sigset_t mask, orig_mask;
sigemptyset (&mask); /* creates and empty mask */
/* Now let's add a few signals to the mask. Others could be considered like SIGTERM) */
sigaddset (&mask, SIGALRM);
sigaddset (&mask, SIGINT);
sigaddset (&mask, SIGQUIT);
/* Now use the mask prepared above to as a "blocking" mask. At the same time, the current mask is saved in orig_mask variable */
if ( pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) != 0 ) {
/* Handle any possible error... *//
}
/*
Call the function that creates the thread in the third party library
*/
/* And now restore the original mask, saved in orig_mask variable */
if ( pthread_sigmask(SIG_SETMASK, &orig_mask, NULL) != 0 ) {
/* Handle any possible error */
}
Conclusion
After applying the code above to the customer application, the problem didn't show up again. Since we're blocking the "dangerous" signals before we create the new thread, it will inherit this blocking. And after we block those signals, the handlers setup by 4GL code will not be called in the context of the "foreign" thread. They'll be run only when the context belongs to the 4GL thread.
This means the "nasty" things 4GL does to it's own stack will not ever affect the other thread(s) stack.
I'd like to point out the difference between technical support services: IBM provided a trial patch to "fix" something that works as design. Another company declined to answer some questions because we mentioned an unsupported version, although the questions were valid for currently supported versions. And another one could not explain the difference between their systems, and only after my insistence they managed to provide some details (that were not very helpful, but I grant that because the problem was not on their side this could be reasonable)
The final important note: This is a "trick". 4GL is inherently not thread safe and you shouldn't assume otherwise. But there is a difference between not being able to run two 4GL threads in the same process and having a 4GL thread with one or more non 4GL threads. You can still have problems, and this workaround may not be usable in some cases, but it can be a simple way out if you're already in the hole (like if you have been using threads inside a 4GL process and somehow things start to fail). If you're not in that hole yet, be advised not to enter it!
For some more technical details about this issue you can check the following IBM tech note:
http://www-01.ibm.com/support/docview.wss?uid=swg21642635
To grant the credits to those who deserve it, the progress on this issue would not have been possible without Rui Mourão (from the customer team) and IBM's technical support colleague Alberto Bortolan
Versão Portuguesa:
Introducão
De volta a uma situação real, desta feita com 4GL. Tem
alguns detalhes técnicos interessantes, e é também um bom exemplo de
como fazer
debug e resolver um problema (modéstia à parte, mas
como verá o mérito não é meu - várias pessoas contribuíram para isto -
). É também um exemplo perfeito de como os suportes técnicos tendem a
trabalhar atualmente, e como costumo dizer, os clientes recebem aquilo
que exigem (menor qualidade e menos disponibilidade para ajudar). E
novamente o suporte técnico da IBM distinguiu-se de outros. E sim, sou
suspeito, mas ao longo do artigo mostrarei as diferenças e poderá julgar
por si mesmo.
Estou a apresentar isto como um aviso para qualquer
cliente que pense em fazer algo semelhante (que não é suportado e
portanto deve ser evitado), e como possível forma de contornar o
problema, caso já o tenha feito e tenha os mesmos problemas.
Não
mencionarei os nomes dos outros fornecedores, visto que os irei criticar
pelo suporte prestado. Devido a isso terei de mascarar alguns detalhes.
Espero que isso não impeça o entendimento do problema
Descrição do problema
O 4GL é uma linguagem de programação que
pode ser integrada com outros componentes através da utilização de
funções em linguagem C. O 4GL não é suposto ser "
thread safe". As
threads
podem ser vistas como auxiliares de programação concorrente. De certa
forma atuam como processos, mas são mais leves e devido a isso são
usadas em muitas situações. No entanto, porque um processo pode correr
várias
threads, todas elas partilham um espaço de endereçamento
comum e os sistemas operativos não as isolam como aos processos (na
verdade, se o fizessem perdiam-se as maiores vantagens que têm). Isto
pode ter más consequências. Temos de ter muito cuidado ao trabalhar num
modelo de programação com
threads, pois uma
thread pode re-escrever dados usados por outra(s)
thread(s) e isto pode dar origem a problemas muito variados. Existem várias técnicas para obter segurança na gestão das
threads como código
re-entrant, exclusão mútua, usar variáveis locais em vez de globais etc. Para que conste, uma
thread tem um
stack próprio onde é guardado o contexto da
thread e algumas variáveis locais por exemplo. O
stack de cada
thread deve ser considerado "solo sagrado" não devendo ser "invadido" por outra(s)
thread(s).
Dizer que o 4GL não é
thread safe
significa que o código interno do 4GL não foi implementado com os
conceitos anteriores em mente. Depende de variáveis globais por exemplo.
Mas também pode fazer "maldades" que afetam outras
threads dentro do mesmo processo, mesmo quando essas
threads não executam funções e/ou código 4GL.
Nesta situação, o cliente tinha
linkado uma aplicação 4GL com uma biblioteca de terceiros (
software de
middleware) que internamente usa
threads (cria uma
thread
para ser exato). Essa biblioteca permite ao 4GL interagir (extraindo
ou requisitando dados) com sistemas externos. É importante notar que
isto foi feito há 5-10 anos atrás e tem funcionado sem que ninguém se
apercebesse de qualquer problema. Em dado momento, devido a sobrecarga
num sistema, o cliente decidiu distribuir alguns utilizadores para outro
sistema, configurado com as mesmas versões de SO, 4GL e da biblioteca
de integração. Ao início detetaram-se diferenças mínimas entre os
sistemas, mas depois de eliminadas o problema persistiu. Nos sistemas
"novos" (na realidade são sistemas antigos que estão já sem novos
desenvolvimentos e em fim de vida) os utilizadores começaram a sentir inúmeros
crashes nas aplicações. Core dumps eram gerados e foram-me reportados para análise.
Tenho
trabalhado com o 4GL desde 1991 (ena... 22 anos!), primeiro como
programador e depois de ter entrado na Infomix (1998) como consultor,
portanto diria que tenho uma vasta experiência com a linguagem (apesar
de atualmente, honestamente me sentir "enferrujado" porque não trabalho
com o produto todos os dias). Mas o que estou a tentar dizer é que
obviamente esta não é a primeira vez que vejo um
core no 4GL. Tipicamente são causados por exceder-mos os limites dos
arrays, ou porque alguma função em C com algum problema foi
linkada com a aplicação, ou muito raramente devido a algum
bug. Portanto, normalmente começo com um
debugger (dbx por exemplo) para extrair o
stack trace.
Normalmente isto diz-nos onde é que o problema aconteceu e isso
permite-nos fazer alguma análise de código. Outra opção é recompilar os
módulos com a opção "-a" do comando
c4gl. Isto inclui algum código de proteção aos limites dos
arrays
que obriga o programa a abortar se os mesmos forem excedidos. Isto
teoricamente pode abrandar o programa, mas é uma excelente forma de
apanhar problemas de dimensionamento de
arrays.
Neste caso particular, o
stack trace
não era muito útil, pois parecia "corrompido", e apenas mostrava
funções de sistema operativo relacionadas com gestão de exceções.
Significava isto que o SO se apercebia que algo tinha corrido mal, mas
durante a geração do
core ficava confuso. A opção "-a" foi usada, mas sem sucesso. Os
crashes pareciam relativamente aleatórios, ocorrendo em diferentes zonas do código e diferentes opções do utilizador.
Foi seguida uma abordagem mais empírica. Pegámos num programa que gerava
cores
frequentemente numa determinada opção e tentámos simplificá-lo ao
máximo. Acabámos por chegar a um caso de teste muito mais simples que
conseguíamos usar para reproduzir o problema. Depois de algumas
repetições da mesma operação obtínhamos o
crash. E tipicamente o
problema aparecia em torno da leitura de uma tecla. O primeiro suspeito
que tivemos foi o 4GL... existe documentação sobre um potencial problema
quando algumas estruturas de input (INPUT, PROMPT,
MENU etc.) são usadas dentro de um ciclo e saímos do mesmo sem sair da
estrutura devidamente. Essencialmente devido a isto (o código de teste
assemelhava-se a uma descrição do problema) decidimos abrir um PMR na
IBM. Foi a primeira interação com qualquer dos suportes técnicos dos
vendedores envolvidos (IBM pelo 4GL; um para o SO e outro para a
biblioteca de integração). Após alguma investigação descartámos esta
hipótese. O suporte técnico da IBM foi útil para entendermos alguns
detalhes que reunimos com o
debugger. Mas durante um curto perído
estávamos bloqueados. Devo explicar que por esta altura éramos três
pessoas bastante envolvidas no assunto: Eu estava a tentar seguir uma
abordagem prática para isolar o problema tentando alterar o caso de
teste e simplificá-lo; Um colega do suporte técnico da IBM que estava a
tentar encontrar situações semelhantes já reportadas e nos dava suporte em
dúvidas várias visto ser muito mais conhecedor de
debuggers; Um
membro da equipa do cliente que estava a seguir a abordagem mais
"científica". Passou horas a tentar executar o processo num
debugger e chegou mesmo a analisar o
assembler (e acreditem-me que ninguém se mantém inalterado depois de tentar entender o
assembler de uma plataforma complexa, antiga e mal documentada)
Pela
minha parte consegui confirmar que se a aplicação não chamasse a
biblioteca de integração o problema não acontecia. A verificação do
código que "colava" o 4GL a essa biblioteca permitiu identificar vários
problemas, mas mesmo depois de corrigidos, o problema mantinha-se. Eu
estava a ficar sem opções... Mas o cliente notou que parecia haver uma
alteração de comportamento nas
threads mesmo antes de o
core ser gerado. Ele estava plenamente convencido que as
threads
ou a gestão das mesmas estavam diretamente relacionados com o
problema. Com estes dados em mente, notámos que as funções da biblioteca
de integração pareciam "desaparecer" do
stack trace do processo após o
crash. Assim, mudámos a investigação das estruturas do 4GL para problemas com
threads. Escusado será dizer que nem eu nem o colega da IBM conseguimos encontrar muitas situações, pois como o 4GL não é
thread safe não há muitas pessoas a usá-lo com
threads. Mas conseguimos encontrar alguns relatos:
- Uma ocorrência na mesma plataforma que o nosso caso. Mas o cliente tinha a opção de linkar com uma biblioteca que não usava threads e o problema foi fechado sem mais investigação
- Um problema semelhante, relacionado com a leitura de teclado, mas em AIX. Foram mudados alguns parâmetros que controlam o thread scheduling e o problema desapareceu
- Um problema não muito bem documentado, em Solaris. Terá sido fornecido um workaround pela IBM sob a forma de patch específico
É importante recordar que tínhamos dois sistemas onde isto
acontecia e outros onde não era possível reproduzir. Os sistemas
pareciam semelhantes e os administradores de sistema não encontravam
explicação para a diferença de comportamentos. Após alguma insistência
concordaram em abrir um caso no suporte do sistema operativo. Expliquei
que procurávamos ajuda para fazer o
debug e que não tínhamos
indicação nenhuma que a origem do problema fosse no SO. Uma espécie de
conferência com possibilidade de partilha do
desktop foi agendada e demonstrei o problema a acontecer. Durante a geração do
core uma mensagem pouco clara (algo como "
unable to unwind the stack")
era mostrada... e assim que a pessoa do suporte a viu escreveu: "Isso é
um problema aplicacional!"... Bom... Sim... Em teoria podia concordar
com isso. Mas tal não nos ajudava. E não explicava porque só acontecia
em alguns sistemas E não explicava porque é que o SO não gerava um
core
"limpo"... Depois de mais alguma conversa, e depois de reforçar que o
que procurávamos era um conhecimento mais profundo nas ferramentas de
debug do SO, e na interpretação dos
outputs,
um outro elemento do suporte juntou-se á conferência. Era claramente
mais experiente e tinha outra atitude, muito mais disposta a ajudar, mas
apesar disso, durante a conferência não se fez magia...
Concordaram
em investigar alguns pontos que levantei sobre o comportamento do SO e
na gestão de sinais (referido nos outros PMRs na IBM), e prosseguimos com
a investigação pela IBM e continuamos a elaboração do caso de teste.
Nesta altura o ponto de situação era:
- Estávamos quase certos que o problema estava relacionado com threads (graças ao elemento da equipa do cliente que nos permitiu encontrar algo relevante na base de dados de casos da IBM)
- Estávamos intrigados pelo facto de o mesmo código ter comportamentos
diferentes em sistemas diferentes. Daí o pedido de ajuda ao suporte do
SO
- Dado que o 4GL não foi pensado para correr num ambiente de múltiplas threads,
tentámos verificar se haveria alguma configuração da biblioteca de
integração que permitisse fazer as mesmas operações, mas sem criar nova thread
O suporte do SO voltou a contactar-nos com alguns detalhes algo vagos. mas nada que nos permitisse modificar o comportamento do
scheduling das
threads (como teria sido feito no caso em AIX). E não conseguiam uma explicação sobre porque só acontecia em alguns sistemas.
Entretanto ficámos interessados na forma como o 4GL trabalha com os
handlers de sinais. Começamos a suspeitar que o 4GL estava a posicionar alguns
handlers de sinais e que um (ou mais) estava a ser executado no contexto da
thread criada pela biblioteca.
Dado
o 3) acima, sugeri que obtivéssemos mais envolvimento da equipa que
gere a biblioteca. Mas como não nos conseguiram dar grandes respostas,
decidimos abrir um caso no suporte do fornecedor da biblioteca. As
questões eram:
- Há alguma forma de evitar a criação da thread?
- Senão, há alguma forma de controlar o tempo que permanece idle (notámos que a thread passava a maior parte do tempo idle e que era acordada a intervalos regulares)
- Haveria alguma forma de controlar os sinais aos quais a thread poderia responder?
Houve apenas uma resposta, e foi desconcertante:
"Desculpe Sr. cliente. Está a usar uma versão sem suporte. Por favor atualize a versão antes de fazer qualquer pergunta".
Não me
interprete mal... Entendo e concordo com o fim de suporte para versões
muito antigas. Mas não estávamos a pedir análise de código,
debug ou alterações ao mesmo. E
as questões (segundo o manual da versão atual as mesmas funções ainda
existiam) seriam válidas para as versões correntes do produto. Portanto
a resposta pareceu um pouco forte. Já presenciei situações muito piores
serem atendidas pelo suporte IBM Informix. Mas o cliente aceitou a
resposta, portanto não podia fazer nada sobre isso. Voltámos
novamente a estar um pouco encravados... Sem muita ajuda de outros
suportes técnicos. O nosso suporte técnico sugeriu que o 4GL, ao ler o
teclado, estava a fazer algo para lidar com a tecla especial "ESCAPE"
que poderia causar problemas... De forma muito resumida, o código de
"escape" pode ser a tecla de "ESCAPE" ou o inicio daquilo que se designa
por sequência de escape (usadas em emuladores de terminais). O código
faz uma chamada à função
read() e obtém o código de escape. E
isso coloca-o num dilema: Se é uma sequência de escape (começa com o
código de escape) o programa tem de voltar a ler (nova chamada
read()).
Mas se era apenas o código da tecla, esta nova chamada vai bloquear
(pois não há nada para ler). A forma como o 4GL resolve isto é chamar a
função
alarm() e depois o segundo
read(). A chamada a
alarm() despoleta um sinal (ALRM) a ser enviado ao processo ao fim do tempo especificado na chamada. Se nessa altura o
read() ainda estiver pendente, o
handler do sinal (estabelecido pelo 4GL) irá cancelar a chamada e prosseguir com a execução normal. Isto é possível manipulando o
stack. É um truque "feio", mas é, ou pelo menos foi comum há alguns anos atrás. Conseguimos encontrar
discussões sobre problemas no Apache porque utiliza o mesmo conceito (naturalmente não para ler o teclado).
Com isto em mente, decidimos tentar um
workaround
mudando o código interno do 4GL. Fiquei impressionado com a capacidade e
a vontade do suporte técnico em tentar isto. O mecanismo anterior foi
modificado para outro método que evita a segunda chamada ao
read() (a que bloqueava). Uma chamada não bloqueante foi usada e criaram um
patch não oficial para teste. Este foi colocado no ambiente do cliente e o programa deixou de
crashar naquele ponto específico. Isto foram as boas notícias. As más é que começamos a ter outros
crashes noutros pontos, e assumindo que estes eram ainda causados pela execução de
handlers de sinais fora do contexto da
thread
do 4GL, não seria possível alterar isso sem efetuar mudanças
estruturais ao código interno do 4GL. E era claro que seria difícil
fazer essa implementação e em princípio o desenvolvimento da IBM não
veria isso como uma possibilidade viável.
A solução
Foi então que uma abordagem mais pragmática foi assumida. A documentação de sistema diz-nos que uma
thread B criada por uma
thread A, herda a "
signal mask" da A. A "
signal mask" é um
array de
bits onde cada um representa um sinal específico, e se estiver
on/off o sinal será/não será recebido pela
thread. Como não queremos que a
thread criada pelo
software de integração receba sinais para os quais o 4GL preparou
handlers, podemos mudar a máscara imediatamente antes de criar a
thread, e restaurar a inicial imediatamente depois de criar a
thread.
O suporte técnico da IBM concordou que valeria a pena testar isto e forneceu uma lista de sinais que valia a pena impedir.
Tecnicamente, fazer isto é muito simples:
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
[…]
sigset_t mask, orig_mask;
sigemptyset (&mask); /* cria uma máscara vazia */
/* Agora vamos adicionar alguns sinais à máscara. Outros poderiam ser considerados como o SIGTERM) */
sigaddset (&mask, SIGALRM);
sigaddset (&mask, SIGINT);
sigaddset (&mask, SIGQUIT);
/* Agora usamos a máscara que preparámos antes como uma máscara de bloqueio. Ao mesmo tempo a máscara actual é salvaguardada na variável orig_mask */
if ( pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) != 0 ) {
/* Lidar com qualquer possível erro.. *//
}
/*
Chamar a função que cria a thread na biblioteca de integração
*/
/* E agora restaurar a máscara original, entretanto slavguardada na variável orig_mask */
if ( pthread_sigmask(SIG_SETMASK, &orig_mask, NULL) != 0 ) {
/* Lidar com qualquer possível erro */
}
Conclusão
Após aplicar a alteração explicada acima na aplicação
do cliente, o problema não se voltou a verificar. Como estamos a
bloquear sinais "perigosos" antes de criarmos a nova
thread, esta irá herdar esse bloqueio. Depois disso os
handlers ativados pelo código 4GL não serão chamados no contexto da
thread "externa". Apenas serão executados quando o contexto corrente pertence à
thread de 4GL.
Isto quer dizer que as coisas "más" que o 4GL faz ao seu próprio
stack nunca irão afetar o
stack da(s) outra(s)
thread(s).
Gostaria de salientar a diferença que verifiquei entre os serviços de suporte técnico: A IBM providenciou um
patch
de teste para "corrigir" algo que funciona como foi desenhado. Outra
companhia declinou responder a algumas questões porque mencionámos uma
versão fora de suporte, ainda que as questões fossem válidas para as
versões que estão suportadas. E outra companhia não pôde explicar as
diferenças de comportamento entre sistemas, e apenas depois da minha
insistência foram capazes de providenciar alguns detalhes (que não foram
muito úteis, mas compreendo que dado o problema não estar do lado deles
isto poderá ser visto como razoável).
Uma nota final importante. Isto é um truque. O 4GL não é, nunca foi e provavelmente nunca será
thread safe. Mas existe uma diferença entre não ser possível correr duas
threads "4GL" no mesmo processo e ter uma
thread 4GL com uma ou mais
threads não 4GL.
Pode mesmo assim haver problemas e esta solução poderá não ser viável
em alguns casos, mas pode ser uma saída se já se encontrar no "buraco"
(caso já venha a usar
threads dentro de processos 4GL e de
repente as coisas começarem a falhar). Se ainda não está nesse buraco,
fica o aviso para não se deixar arrastar para ele!
Para mais detalhes técnicos sobre este problema pode consultar a seguinte nota técnica da IBM:
http://www-01.ibm.com/support/docview.wss?uid=swg21642635
Para
dar o crédito a quem o merece, a evolução deste problema só foi
possível graças à participação do Rui Mourão (pelo cliente) e do colega
do suporte técnico da IBM Alberto Bortolan