浏览代码

* thread-safe time zone info read/write

git-svn-id: trunk@47309 -
(cherry picked from commit 7fbceaac2193b92552f631669ab8f20862f6e813)
ondrej 4 年之前
父节点
当前提交
eec56c20c8
共有 4 个文件被更改,包括 183 次插入77 次删除
  1. 17 12
      rtl/android/unixandroid.inc
  2. 4 3
      rtl/unix/sysutils.pp
  3. 59 28
      rtl/unix/timezone.inc
  4. 103 34
      rtl/unix/unix.pp

+ 17 - 12
rtl/android/unixandroid.inc

@@ -41,10 +41,6 @@ var
   ucal_inDaylightTime: function (cal: UCalendar; var status: UErrorCode): UBool; cdecl;
   ucal_get: function (cal: UCalendar; field: UCalendarDateFields; var status: UErrorCode): int32_t; cdecl;
 
-var
-  TZStandardName: utf8string;
-  TZDaylightName: utf8string;
-
   GetIcuProc: function (const Name: AnsiString; var ProcPtr; libId: longint): boolean; external name 'ANDROID_GET_ICU_PROC';
 
 procedure ReadTimeZoneFromICU;
@@ -52,8 +48,12 @@ var
   locale: utf8string;
   tz: unicodestring;
   res: unicodestring;
+  TZStandardName: utf8string;
+  TZDaylightName: utf8string;
   err: UErrorCode;
   cal: UCalendar;
+  lTZInfo: TTZInfo;
+  lTZInfoEx: TTZInfo;
 begin
   if not Assigned(GetIcuProc) then exit;
   if not GetIcuProc('ucal_open', ucal_open, 1) then exit;
@@ -68,25 +68,30 @@ begin
   cal:=ucal_open(PUnicodeChar(tz), Length(tz), PAnsiChar(locale), 0, err);
   if cal = nil then
     exit;
-  Tzinfo.daylight:=ucal_inDaylightTime(cal, err);
+  lTzinfo.daylight:=ucal_inDaylightTime(cal, err);
 
   SetLength(res, 200);
   SetLength(res, ucal_getTimeZoneDisplayName(cal, UCAL_SHORT_STANDARD, PAnsiChar(locale), PUnicodeChar(res), Length(res), err));
   TZStandardName:=utf8string(res);
-  Tzinfo.name[False]:=PAnsiChar(TZStandardName);
+  lTZInfoEx.name[False]:=TZStandardName;
 
   SetLength(res, 200);
   SetLength(res, ucal_getTimeZoneDisplayName(cal, UCAL_SHORT_DST, PAnsiChar(locale), PUnicodeChar(res), Length(res), err));
   TZDaylightName:=utf8string(res);
-  Tzinfo.name[True]:=PAnsiChar(TZDaylightName);
+  lTZInfoEx.name[True]:=TZDaylightName;
 
-  Tzinfo.seconds:=ucal_get(cal, UCAL_ZONE_OFFSET, err) div 1000;
-  if Tzinfo.daylight then
-    Tzinfo.seconds:=Tzinfo.seconds + ucal_get(cal, UCAL_DST_OFFSET, err) div 1000;
+  lTZInfoEx.leap_correct:=0;
+  lTZInfoEx.leap_hit:=0;
+
+  lTZInfo.seconds:=ucal_get(cal, UCAL_ZONE_OFFSET, err) div 1000;
+  if lTZInfo.daylight then
+    lTZInfo.seconds:=Tzinfo.seconds + ucal_get(cal, UCAL_DST_OFFSET, err) div 1000;
 
   // ToDo: correct validsince/validuntil values
-  Tzinfo.validsince:=low(Tzinfo.validsince);
-  Tzinfo.validuntil:=high(Tzinfo.validuntil);
+  lTZInfo.validsince:=low(lTZInfo.validsince);
+  lTZInfo.validuntil:=high(lTZInfo.validuntil);
+
+  SetTZInfo(lTZInfo, lTZInfoEx);
 
   ucal_close(cal);
 end;

+ 4 - 3
rtl/unix/sysutils.pp

@@ -1634,13 +1634,14 @@ begin
   else
     UnixTime:=LocalToEpoch(Year, Month, Day, Hour, Minute, Second);
   { check if time is in current global Tzinfo }
-  if (Tzinfo.validsince<UnixTime) and (UnixTime<Tzinfo.validuntil) then
+  lTzinfo:=Tzinfo;
+  if (lTzinfo.validsince<=UnixTime) and (UnixTime<lTzinfo.validuntil) then
   begin
     Result:=True;
-    Offset:=-TZInfo.seconds div 60;
+    Offset:=-lTZInfo.seconds div 60;
   end else
   begin
-    Result:=GetLocalTimezone(UnixTime,True,lTZInfo,False);
+    Result:=GetLocalTimezone(UnixTime,True,lTZInfo);
     if Result then
       Offset:=-lTZInfo.seconds div 60;
   end;

+ 59 - 28
rtl/unix/timezone.inc

@@ -75,64 +75,93 @@ begin
 end;
 
 
-function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
-var
-  info : pttinfo;
-  i,trans_start,trans_end : longint;
+procedure DoGetLocalTimezone(info:pttinfo;const trans_start,trans_end:longint;var ATZInfo:TTZInfo);
 begin
-{ reset }
-  ATZInfo.Daylight:=false;
-  ATZInfo.Seconds:=0;
-  ATZInfo.Name[false]:=nil;
-  ATZInfo.Name[true]:=nil;
-  ATZInfo.validsince:=0;
-  ATZInfo.validuntil:=0;
-  ATZInfo.leap_correct:=0;
-  ATZInfo.leap_hit:=0;
-{ get info }
-  info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
-  GetLocalTimezone:=assigned(info);
-  if not GetLocalTimezone then
-   exit;
   ATZInfo.validsince:=trans_start;
   ATZInfo.validuntil:=trans_end;
   ATZInfo.Daylight:=info^.isdst;
   ATZInfo.Seconds:=info^.offset;
-  if not FullInfo then
-    Exit;
+end;
+
+procedure DoGetLocalTimezoneEx(timer:cint;info:pttinfo;var ATZInfoEx:TTZInfoEx);
+var
+  i : longint;
+  names: array[Boolean] of pchar;
+begin
+  names[true]:=nil;
+  names[false]:=nil;
+  ATZInfoEx.leap_hit:=0;
+  ATZInfoEx.leap_correct:=0;
+
   i:=0;
   while (i<num_types) do
    begin
-     ATZInfo.name[types[i].isdst]:=@zone_names[types[i].idx];
+     names[types[i].isdst]:=@zone_names[types[i].idx];
      inc(i);
    end;
-  ATZInfo.name[info^.isdst]:=@zone_names[info^.idx];
+  names[info^.isdst]:=@zone_names[info^.idx];
+  ATZInfoEx.name[true]:=names[true];
+  ATZInfoEx.name[false]:=names[false];
   i:=num_leaps;
   repeat
     if i=0 then
      exit;
     dec(i);
   until (timer>leaps[i].transition);
-  ATZInfo.leap_correct:=leaps[i].change;
+  ATZInfoEx.leap_correct:=leaps[i].change;
   if (timer=leaps[i].transition) and
      (((i=0) and (leaps[i].change>0)) or
       (leaps[i].change>leaps[i-1].change)) then
    begin
-     ATZInfo.leap_hit:=1;
+     ATZInfoEx.leap_hit:=1;
      while (i>0) and
            (leaps[i].transition=leaps[i-1].transition+1) and
            (leaps[i].change=leaps[i-1].change+1) do
       begin
-        inc(ATZInfo.leap_hit);
+        inc(ATZInfoEx.leap_hit);
         dec(i);
       end;
    end;
 end;
 
+function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo):Boolean;
+var
+  info: pttinfo;
+  trans_start,trans_end: longint;
+begin
+  LockTZInfo;
+  info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
+  GetLocalTimezone:=assigned(info);
+  if GetLocalTimezone then
+    DoGetLocalTimezone(info,trans_start,trans_end,ATZInfo);
+  UnlockTZInfo;
+end;
 
-procedure GetLocalTimezone(timer:cint;timerIsUTC:Boolean);
+function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;var ATZInfoEx:TTZInfoEx):Boolean;
+var
+  info: pttinfo;
+  trans_start,trans_end: longint;
+begin
+  LockTZInfo;
+  info:=find_transition(timer,timerIsUTC,trans_start,trans_end);
+  GetLocalTimezone:=assigned(info);
+  if GetLocalTimezone then
+    begin
+    DoGetLocalTimezone(info,trans_start,trans_end,ATZInfo);
+    DoGetLocalTimezoneEx(timer,info,ATZInfoEx);
+    end;
+  UnlockTZInfo;
+end;
+
+procedure RefreshTZInfo;
+var
+  NewTZInfo: TTZInfo;
+  NewTZInfoEx: TTZInfoEx;
 begin
-  GetLocalTimezone(timer,timerIsUTC,Tzinfo,true);
+  LockTZInfo;
+  if GetLocalTimezone(fptime,false,NewTZInfo,NewTZInfoEx) then
+    SetTZInfo(NewTZInfo,NewTZInfoEx);
+  UnlockTZInfo;
 end;
 
 Const
@@ -352,7 +381,7 @@ end;
 procedure InitLocalTime;
 begin
   ReadTimezoneFile(GetTimezoneFile);
-  GetLocalTimezone(fptime,false);
+  RefreshTZInfo;
 end;
 
 
@@ -381,6 +410,8 @@ end;
 Procedure ReReadLocalTime;
 
 begin
+  LockTZInfo;
   DoneLocalTime;
   InitLocalTime;
+  UnlockTZInfo;
 end;

+ 103 - 34
rtl/unix/unix.pp

@@ -56,23 +56,27 @@ Const
 type
   TTZInfo = record
     daylight     : boolean;
-    name         : array[boolean] of pchar;
     seconds      : Longint; // difference from UTC
     validsince   : int64;   // UTC timestamp
     validuntil   : int64;   // UTC timestamp
+  end;
+  TTZInfoEx = record
+    name         : array[boolean] of RawByteString; { False = StandardName, True = DaylightName }
     leap_correct : longint;
     leap_hit     : longint;
   end;
 
-var
-  Tzinfo : TTZInfo;
-
   Function GetTzseconds : Longint;
   property Tzseconds : Longint read GetTzseconds;
   function Gettzdaylight : boolean;
-  function Gettzname(const b : boolean) : pchar;
   property tzdaylight : boolean read Gettzdaylight;
-  property tzname[b : boolean] : pchar read Gettzname;
+  function Gettzname(const b : boolean) : string;
+  property tzname[b : boolean] : string read Gettzname;
+  function GetTZInfo : TTZInfo;
+  property TZInfo : TTZInfo read GetTZInfo;
+  function GetTZInfoEx : TTZInfoEx;
+  property TZInfoEx : TTZInfoEx read GetTZInfoEx;
+  procedure SetTZInfo(const ATZInfo: TTZInfo; const ATZInfoEx: TTZInfoEx);
 
 {************     Procedure/Functions     ************}
 
@@ -84,14 +88,14 @@ var
                        // it doesn't (yet) work for.
 
 { timezone support }
-function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;FullInfo:Boolean):Boolean;
-procedure GetLocalTimezone(timer:cint;timerIsUTC:Boolean);
+function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo;var ATZInfoEx:TTZInfoEx):Boolean;
+function GetLocalTimezone(timer:cint;timerIsUTC:Boolean;var ATZInfo:TTZInfo):Boolean;
+procedure RefreshTZInfo;
 procedure ReadTimezoneFile(fn:string);
 function  GetTimezoneFile:string;
 Procedure ReReadLocalTime;
 {$ENDIF}
 
-Procedure RefreshTZInfoIfNeeded;
 Function UniversalToEpoch(year,month,day,hour,minute,second:Word):int64; // use DateUtils.DateTimeToUnix for cross-platform applications
 Function LocalToEpoch(year,month,day,hour,minute,second:Word):int64; // use DateUtils.DateTimeToUnix for cross-platform applications
 Procedure EpochToLocal(epoch:int64;var year,month,day,hour,minute,second:Word); // use DateUtils.UnixToDateTime for cross-platform applications
@@ -187,6 +191,31 @@ Function getenv(name:string):Pchar; external name 'FPC_SYSC_FPGETENV';
                           timezone support
 ******************************************************************************}
 
+var
+  CurrentTZinfo : array [0..1] of TTZInfo;
+  CurrentTzinfoEx : array [0..1] of TTZInfoEx;
+  CurrentTZindex : LongInt = 0; // current index for CurrentTZinfo/CurrentTZinfoEx - can be only 0 or 1
+{$ifdef FPC_HAS_FEATURE_THREADING}
+  UseTZThreading: Boolean = false;
+  TZInfoCS: TRTLCriticalSection;
+{$endif}
+
+procedure LockTZInfo;
+begin
+  {$if declared(UseTZThreading)}
+  if UseTZThreading then
+    EnterCriticalSection(TZInfoCS);
+  {$endif}
+end;
+
+procedure UnlockTZInfo;
+begin
+  {$if declared(UseTZThreading)}
+  if UseTZThreading then
+    LeaveCriticalSection(TZInfoCS);
+  {$endif}
+end;
+
 Function GetTzseconds : Longint;
 begin
   GetTzseconds:=Tzinfo.seconds;
@@ -197,9 +226,43 @@ begin
   Gettzdaylight:=Tzinfo.daylight;
 end;
 
-function Gettzname(const b : boolean) : pchar;
+function Gettzname(const b : boolean) : string;
+begin
+  Gettzname:=TzinfoEx.name[b];
+end;
+
+function GetTZInfo : TTZInfo;
+var
+  curtime: time_t;
 begin
-  Gettzname:=Tzinfo.name[b];
+  GetTZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
+  curtime:=fptime;
+  if not((GetTZInfo.validsince+GetTZInfo.seconds<=curtime) and (curtime<GetTZInfo.validuntil+GetTZInfo.seconds)) then
+    begin
+    RefreshTZInfo;
+    GetTZInfo:=CurrentTZinfo[InterlockedExchangeAdd(CurrentTZindex, 0)];
+    end;
+end;
+
+function GetTZInfoEx : TTZInfoEx;
+begin
+  GetTZInfoEx:=CurrentTzinfoEx[InterlockedExchangeAdd(CurrentTZindex, 0)];
+end;
+
+procedure SetTZInfo(const ATZInfo: TTZInfo; const ATZInfoEx: TTZInfoEx);
+var
+  OldTZindex,NewTZindex: longint;
+begin
+  LockTZInfo;
+  OldTZindex:=InterlockedExchangeAdd(CurrentTZindex,0);
+  if OldTZindex=0 then
+    NewTZindex:=1
+  else
+    NewTZindex:=0;
+  CurrentTzinfo[NewTZindex]:=ATZInfo;
+  CurrentTzinfoEx[NewTZindex]:=ATZInfoEx;
+  InterlockedExchangeAdd(CurrentTZindex,NewTZindex-OldTZindex);
+  UnlockTZInfo;
 end;
 
 Const
@@ -235,20 +298,22 @@ Procedure EpochToLocal(epoch:Int64;var year,month,day,hour,minute,second:Word);
   Transforms Epoch time into local time (hour, minute,seconds)
 }
 Var
-  DateNum: LongInt;
   lTZInfo: TTZInfo;
+  lseconds: LongInt;
 Begin
   { check if time is in current global Tzinfo }
-  if (Tzinfo.validsince<epoch) and (epoch<Tzinfo.validuntil) then
-    inc(Epoch,TZInfo.seconds)
+  lTZInfo:=TZInfo;
+  lseconds:=lTZInfo.seconds;
+  if (lTZInfo.validsince<=epoch) and (epoch<lTZInfo.validuntil) then
+    inc(Epoch,lseconds)
   else
   begin
     {$if declared(GetLocalTimezone)}
-    if GetLocalTimezone(epoch,true,lTZInfo,false) then
+    if GetLocalTimezone(epoch,true,lTZInfo) then
       inc(Epoch,lTZInfo.seconds)
     else { fallback }
     {$endif}
-      inc(Epoch,TZInfo.seconds);
+      inc(Epoch,lseconds);
   end;
 
   EpochToUniversal(epoch,year,month,day,hour,minute,second);
@@ -278,19 +343,22 @@ Function LocalToEpoch(year,month,day,hour,minute,second:Word):Int64;
 Var
   lTZInfo: TTZInfo;
   UniversalEpoch: Int64;
+  lseconds: LongInt;
 Begin
   UniversalEpoch:=UniversalToEpoch(year,month,day,hour,minute,second);
   { check if time is in current global Tzinfo }
-  if (Tzinfo.validsince<UniversalEpoch-Tzinfo.seconds) and (UniversalEpoch-Tzinfo.seconds<Tzinfo.validuntil) then
-    LocalToEpoch:=UniversalEpoch-TZInfo.seconds
+  lTZInfo:=TZInfo;
+  lseconds:=lTZInfo.seconds;
+  if (lTZInfo.validsince<=UniversalEpoch-lTZInfo.seconds) and (UniversalEpoch-lTZInfo.seconds<lTZInfo.validuntil) then
+    LocalToEpoch:=UniversalEpoch-lseconds
   else
   begin
     {$if declared(GetLocalTimezone)}
-    if GetLocalTimezone(LocalToEpoch,false,lTZInfo,false) then
+    if GetLocalTimezone(UniversalEpoch,false,lTZInfo) then
       LocalToEpoch:=UniversalEpoch-lTZInfo.seconds
     else { fallback }
     {$endif}
-      LocalToEpoch:=UniversalEpoch-TZInfo.seconds
+      LocalToEpoch:=UniversalEpoch-lseconds;
   end;
 End;
 
@@ -319,20 +387,6 @@ Begin
   GregorianToJulian:=((((Month*153)+2) div 5)+Day)+D2+XYear+Century;
 End;
 
-Procedure RefreshTZInfoIfNeeded;
-{$if declared(ReReadLocalTime)}
-var
-  curtime: time_t;
-begin
-  curtime:=fptime;
-  if ((curtime<Tzinfo.validsince+Tzinfo.seconds) or (curtime>Tzinfo.validuntil+Tzinfo.seconds)) then
-    GetLocalTimezone(fptime,false);
-end;
-{$else}
-begin
-end;
-{$endif}
-
 {******************************************************************************
                           Process related calls
 ******************************************************************************}
@@ -1408,7 +1462,18 @@ end;
   {$I unixandroid.inc}
 {$endif android}
 
+{$if declared(UseTZThreading)}
+procedure InitTZThreading;
+begin
+  UseTZThreading:=True;
+  InitCriticalSection(TZInfoCS);
+end;
+{$endif}
+
 Initialization
+{$if declared(UseTZThreading)}
+  RegisterLazyInitThreadingProc(@InitTZThreading);
+{$endif}
 {$IFNDEF DONT_READ_TIMEZONE}
   InitLocalTime;
 {$endif}
@@ -1420,4 +1485,8 @@ finalization
 {$IFNDEF DONT_READ_TIMEZONE}
   DoneLocalTime;
 {$endif}
+{$if declared(UseTZThreading)}
+  if UseTZThreading then
+    DoneCriticalSection(TZInfoCS);
+{$endif}
 End.