HOAB

History of a bug

synchronisation via SELECT FOR UPDATE et données non mises à jour !

Rédigé par gorki - -

Le problème :

Un problème tout simple ou presque :)

Deux threads qui discutent avec la base de données, synchronisés par un "SELECT ... FOR UPDATE" sur le même objet.

Le deuxième thread n'a pas toujours le résultat du travail du premier thread !

Solution :

Alors j'écarte toutes les hypothèses qui m'ont été faites :

  • une seule transaction par thread de la prise de lock au commit
  • transaction en REQUIRED_NEW
  • le lock via le SELECT FOR UPDATE est la première instruction des threads
  • pas d'instruction DDL pendant les threads
  • le SELECT FOR UPDATE fonctionne correctement

La réponse : Mysql utilise par défaut le mode d'isolation READ REPEATABLE alors que la plupart des autres bases utilisent READ COMMITED.

Comme j'imagine que ce n'est pas évident, quelques explications :

Thread n°1 Thread n°2 Mysql Mode : REPEATABLE READ Mysql Mode : READ COMMITED
SELECT tableLock FOR UPDATE WHERE id = 1      
UPDATE user.status = 4      
start thread n°2      
  SELECT tableLock FOR UPDATE WHERE id = 1 Snapshot de la base pour avoir des données constantes pendant la transaction Pas de snapshot, ce sont les dernières données en base
Commit      
  Obtention du lock APRES le commit    
  select user.status status est null / non mis à jour status = 4 !

Remarques :

  • Le thread n°2 déclenché par le thread n°1 se met en attente du lock. En faisant un SELECT !. C'est là le problème. Le mode REPEATABLE READ fait un snapshot valable sur la base entière au premier select.
  • ce point (snapshot sur la base entière) n'est pas très explicite dans les documentations

Solutions :

  • passer MYSQL en mode READ COMMITED (un peu performant en plus...)
  • mettre les mises à jour des données autres dans des sous-transactions
  • envisager un autre mode de synchronisation hors transaction (cache distribué...)

 

RestAssured Junit et tests Session

Rédigé par gorki - -

Le problème :

RestAssured est un framework de tests pour les services Rest avec un langage assez simple.

Le seul problème est qu'il demande à ce qu'un serveur soit démarré, ce qui est toujours un peu lent pour les tests unitaires.

Heureusement, est inclu RestAssuredMockMvc qui permet de mocker le serveur, faire "comme si" on avait un serveur, très bien !

Problème :

  • Dans un de mes tests j'utilisent des informations stockées en session

Autant RestAssured a prévu le coup et a des méthodes pour ça (cf SessionFilter) côté client, autant la partie RestassuredMockMvc n'a rien de tout ça dans la partie serveur simulé.

Solution :

J'ai pas mal tatonné/recherché, mais finalement une solution assez simple et non intrusive :

// Création de la requête de test avec serveur simulé (le given() de RestassuredMockMvc)
MockMvcRequestSpecification request = given().standaloneSetup(controller);

// Création d'un session vide
final MockHttpSession session = new MockHttpSession();

// On ajoute un interceptor pour indiquer lors de la construction de la requête d'utiliser notre session
request.interceptor(new MockHttpServletRequestBuilderInterceptor() {
            @Override
            public void intercept(MockHttpServletRequestBuilder requestBuilder) {
                requestBuilder.session(session);
            }
});

// on peut ensuite enchainer les appels, la session sera la même

request.queryParam(...).when(...)...

request.queryParam(...).when(...)...

Donc côté serveur simulé, on intercepte le constructeur pour lui donner une session vide (mais non null). On pourrait imaginer un système plus complexe pour gérer le fait qu'au premier appel la session devrait être null, mais on va s'arrêter là pour l'instant.

 

 

PHP Soapclient et connexion HTTPS via un proxy

Rédigé par gorki - -

Le problème :

En passant via mon proxy entreprise ou local (SQUID) mes requêtes SOAP recevaient les erreurs :

  • Parsing WSDL: Couldn't load from 'https://host/service?wsdl' : failed to load external entity 'https://host/service?wsdl'
  • Could not connect to host

Solution :

Trouver l'origine du problème, activer les traces :

$client = new SoapClient("http://www.webservicex.net/ConverPower.asmx?WSDL", array('trace' => 1));
echo "====== REQUEST HEADERS =====" . PHP_EOL;
var_dump($client->__getLastRequestHeaders());
echo "========= REQUEST ==========" . PHP_EOL;
var_dump($client->__getLastRequest());
echo "========= RESPONSE =========" . PHP_EOL;
var_dump($response);
array
(
    'trace' => 1
    'proxy_host' => 127.0.0.1
    'proxy_port' => 3128
)
// Attention ! vérifier que ces directives sont bien appelées (par exemple pas dans un fichier classe... sifflotements...)
ini_set('soap.wsdl_cache_enabled',0);
ini_set('soap.wsdl_cache_ttl',0)
$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true
    )
));

$client  = new SoapClient(null, array( 
    'location' => 'https://...',
    'uri' => '...', 
    'stream_context' => $context
));

 

 

DNS et propagation lente

Rédigé par gorki - -

Le problème :

Avec un serveur dédié chez OVH, il arrive que l'on veuille gérer son DNS directement.

Donc le DNS primaire est votre serveur, OVH propose des DNS secondaires qui se synchronise avec vous. En général ces DNS secondaires sont plus utilisés par le reste du WEB que le votre.

Le serveur secondaire de chez OVH ne propagait pas mes modifications

Solution :

Bête comme choux, mais encore faut-il ne pas l'oublier, les enregistrements DNS ont un timestamp.

Il faut l'incrémenter à chaque fois, c'est sur ce critère que le DNS secondaire se remet à jour.

(On aurait aussi pu utiliser des outils tout fait, qui, eux, n'oublie pas ça !)

Sinon, éditer : /etc/bind/pri/mondomaine.fr et incrémenter la date (2015010107)

mondomaine.fr.	IN	SOA	mondomaine.fr. postmaster.mondomaine.fr. (
			2015010107
			21600
			3600
			604800
			86400 )
                IN      NS      ns1234.ovh.net.
                IN      NS      sdns2.ovh.net.
                IN      MX      10 mail.mondomaine.fr.
                IN      A       1.1.1.1

Ensuite

/etc/init.d/name reload
# ou tout autre commande suivant la version de votre système

 

 

Postfix et 10/10 aux tests antispam

Rédigé par gorki - -

Le problème :

Après avoir installé un serveur de mail. Gmail refusait les mails envoyés depuis ce serveur.

"Our system has detected that this message does 550-5.7.1 not meet IPv6 sending guidelines regarding PTR records and 550-5.7.1 authentication. Please review 550-5.7.1  https://support.google.com/mail/?p=ipv6_authentication_error for more 550 5.7.1 information"

Et là, il faut suivre les conseils vagues de Google, heureusement d'autres sites sont de meilleurs aides, exemple :  https://www.mail-tester.com/

Le contexte :

- un serveur de mail + un nom de domaine (MONDOMAINE)

- un deuxième serveur de mail qui envoie des mails sur ce même domaine

Solution :

Suivre les recommendations du site mail-tester :)

Cela m'a conduit à :

Déclarer des enregistrements DNS sur MONDOMAINE pour autoriser mon deuxième serveur à envoyer des mails :

SERVEUR2.MONDOMAINE IN    A   IP-SERVEUR-2
MONDOMAINE.	IN	TXT	"v=spf1 ip4:IP-SERVEUR-2 include:mx.ovh.com"
MONDOMAINE.	IN	SPF	"v=spf1 ip4:IP-SERVEUR-2 include:mx.ovh.com"

Vérifier que le postfix de mon 2eme serveur est identifié avec SERVEUR2.MONDOMAINE

  • maintenant les serveurs SMTP reçevant nos mails savent que le deuxième serveur est autorisé à les envoyé.
  • que le nom de domaine SERVEUR2.MONDOMAINE est cohérent avec qui est déclaré dans postfix
  • ne pas avoir configurer postfix en openrelay, mais ça il faut l'avoir fait exprès

Il faut aussi installer des signatures DKIM en sortant :

mail._domainkey.MONDOMAINE.	IN TXT    ( "v=DKIM1;t=s;k=rsa; p=clepublic" )
  • on aurait pu avoir glustork à la place comme selecteur ! C'est aussi le principe d'avoir X domaines et X sélecteur pour avoir des clés de chiffrement différentes
glustork._domainkey.MONDOMAINE.	IN TXT    ( "v=DKIM1;t=s;k=rsa; p=clepublic" )
  • des outis pour vérifier les DNS existent sur le Web (ici, , ...)
  • mais un petit DIG suffit aussi :
dig mail._domainkey.<domaine>.fr TXT @dnsserver.com