2012-02-08 11 views
4

私たちは一度完了した確認メールを送信する登録型システムを持っています。システムには数分で約3000件の登録があり、バグに気付きました。ユーザーBが登録してから数ミリ秒後にユーザーAが登録すると、ユーザーAはユーザーBの詳細を電子メールで取得します。私たちはこの問題を解決することができました。私はこのコードを、キャッシュから電子メールテンプレートを取得し、プレースホルダー上の文字列置換を行うコードに絞り込みました。テキスト文字列を置き換えるときの奇妙なC#のバグ

private string ProcessEmailBody(MyRegistrationModel registration) 
{ 
    var content = CacheHelper.GetContent("REGISTRATIONEMAIL"); 

    if (content != null) 
    { 
     content.Text = context.Text.Replace("@@[email protected]@", registration.FullName); 

     return content.Text; 
    } 
    else return null; 
} 

CacheHelper.GetContent()方法は静的であり、私はこれを行うことによって、この「バグ」を修正:

private string ProcessEmailBody(MyRegistrationModel registration) 
{ 
    var content = CacheHelper.GetContent("REGISTRATIONEMAIL"); 

    if (content != null) 
    { 
     string body = content.Text; 
     body = body.Replace("@@[email protected]@", registration.FullName); 

     return body; 
    } 
    else return null; 
} 

そして、これが問題を修正した理由私は私の人生のために把握することはできません。誰にでもこれを見せてもらえますか?

EDIT:ここに私のGetContentメソッド()メソッド(私は簡単にされていた、署名は上記と異なることを知っている)

public static Content GetContent(string key, int partnerSiteId, int? version, IContentRepository contentRepository, out string cacheKey) 
{ 
    cacheKey = string.Format("{0}_{1}_{2}", key, partnerSiteId, version); 

    var content = CacheManager.Get(cacheKey,() => contentRepository.GetContent(key, partnerSiteId, version), WebConfig.GetCacheDuration(CacheProfile.Short)); 

    return content; 
} 

private static DataCache _Cache = null; // DataCache is from AppFabric (Microsoft.ApplicationServer.Caching) 

public static T Get<T>(string objectKey, Func<T> reloadItemExpresion, TimeSpan cacheDuration) where T : class 
{ 
    if (_Cache == null) 
    { 
     if (reloadItemExpresion != null) 
     { 
      return reloadItemExpresion.Invoke(); 
     } 

     return null; 
    } 

    object cachedObject = null; 

    try 
    { 
     cachedObject = _Cache.Get(objectKey); 
    } 
    catch (Exception ex) 
    { 
     if (ex is FileNotFoundException) 
     { 
      _Cache.Remove(objectKey); 
     } 
    } 

    if (cachedObject != null) 
    { 
     return cachedObject as T; 
    } 

    if (reloadItemExpresion != null && cacheDuration > TimeSpan.Zero) 
    { 
     T item = reloadItemExpresion.Invoke(); 

     if (item != null) 
     { 
      Insert(item, objectKey, cacheDuration); 
     } 

     return item; 
    } 

    return null; 
} 

contentRepository.GetContentだけで、データベースへの消灯だと戻って実際のコンテンツをフェッチします。

+3

「CacheHelper.GetContent」のコードを公開します...おそらく、複数のスレッド間でインスタンス*共有*を提供する可能性が高いため、content.Textを変更するときに競合条件を作成しています。 –

+0

十分な情報がありません。コンテンツの実際のタイプは何ですか?'GetContent'メソッドはどのように実装されていますか?あなたは常に 'content'の同じインスタンスを渡しますか?このコードはマルチスレッドで動作しますか?その場合、これは単に競合状態になる可能性があり、適切なスレッド同期を確保する必要があります。 –

+0

私は自分の質問を編集しました。 – eth0

答えて

3

最初にcontext.Text"@@[email protected]@"タグを最初のユーザーの詳細と置き換えます。一度それをしたら、再び"@@[email protected]@"に戻ることはないので、キャッシュがリセットされるまで誰もがその仲間の詳細を取得します。あなたがキャッシュから取得したオブジェクトを変更することは避けてください:

private string ProcessEmailBody(MyRegistrationModel registration) { 
    var content = CacheHelper.GetContent("REGISTRATIONEMAIL"); 
    return content != null ? content.Replace("@@[email protected]@", registration.FullName) : null; 
} 
+0

私は自分の質問を編集しました。 – eth0

+0

@ eth0何も変更されていません。あなたのコードは 'WebConfig.GetCacheDuration(CacheProfile.Short) 'で定義された短い時間間隔内に' CacheHelper'を呼び出すすべてのリクエストに返される共有インスタンスを変更し、キャッシュされた値おそらくリセットされます。キャッシュの持続時間を長くすると、最初のユーザーのコンテンツがより多くのユーザーに表示されます。 – dasblinkenlight

+0

私はそれが理にかなっていると思います。私はキャッシュタイムアウトを60秒に設定しましたが、6秒以上の2回の登録を見たことはありませんでした。それがなぜなのか? – eth0

1

CacheHelperメソッドの仕組みやタイプがわからないと、言い難いcontentです。しかし、それは文字列を返す代わりに、参照によって何らかの種類のContentオブジェクトを返しているようです。したがって、2つのスレッドが同時に実行されている場合、両方ともCacheHelperによって返された同じContentオブジェクトを使用している可能性があります。

新しいContentテンプレートを呼び出すたびにCacheHelperが担当していないと仮定すると、元のコードはすべての置換呼び出しがそれから派生した文字列ではなくTEMPLATEを変更するという意味でバグがあります。私はこのコードを推測している

も動作します:

string body = content.Text.Replace("@@[email protected]@", registration.FullName); 
return body; 

重要なビットは、ローカル変数にコンテンツのテキストを読んでいないが、それは明らかに共有されているコンテンツのTextプロパティを、交換していません。

2

問題は、あなたのcontentオブジェクトがそれにアクセスするすべての人の間で共有オブジェクトであるということです。最初のアプローチcontent.Text = context.Text.Replace()を使用すると、同時にアクセスしているすべての人のテキストを変更することになります。

2つ目のアプローチでは、共有オブジェクト内のテキストを変更しないで、すべての人が同時に自分のテキストを取得します。将来この問題を回避するには、コンストラクタでのみテキストを設定したり、読み取り専用アクセスでインターフェイスを提供したりすることで、content.Textプロパティをコンシューマに対して読み取り専用にすることを検討する必要があります。したがって、コンパイル時にもこのバグは避けてください。

関連する問題