swisseph 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3bd5d41ff56c59145864cde33f26a5cf624b821f12261b45bf6c6c32aa0006b
4
- data.tar.gz: b0c23352d9117a5f89b72b02c72785e6ab30e768552a52161363ad98ac53bfd5
3
+ metadata.gz: 4d6f0a41f6d7670ef96d6b2d55ecaef8e052654a92e8e860ac7f8f42c72fe5c5
4
+ data.tar.gz: e4eb00865e165d73f78680e0d66421655352af5c6dbfaa09f7e84fb77f4dd32a
5
5
  SHA512:
6
- metadata.gz: 1691fd37c14307225d04365279d8f0ba96b4cd95e9ed6ca4ec769bcb8c9a350238f00f9d68084fd0c1a9ad0347022cdb721b08abbfb71100645e1a372def5b8e
7
- data.tar.gz: 75dce6e93170c127ecb97eb57660357046864128ede073e13913c97dba0b3357cad69ebd428d39fecd3c60efe01de696ab02b63c6c3a19938c4a159f2cc164a5
6
+ metadata.gz: d4d06845c78d2eb0f5588aa1fde5c59266483ee1b8f6b9351ae601df8a6768ff4085ff7ac3fce3483f8aeb5de5558592c5ab4ff11427f50dd9c186f9ae7c3178
7
+ data.tar.gz: 2a9a8daff77a5540280fb4c899efb99764abcf1e99434a605dea463f0bca61fcd049eac9da43152306e9acff33002addc84d79c0d8c50e06762d287e0ba2c15e
@@ -54,6 +54,21 @@ if !swisseph_sources_exist || !File.exist?(stamp_file)
54
54
  fetch_swisseph_with_cmake
55
55
  end
56
56
 
57
+ # Enable thread-local `swed` on Apple too. Upstream sweodef.h gates TLS off for
58
+ # __APPLE__ -- a legacy guard for Apple's old GCC 4.2, which lacked __thread.
59
+ # Modern clang supports it on Intel and Apple Silicon, so dropping the exclusion
60
+ # makes libswe per-thread reentrant on macOS as it already is on Linux (verified
61
+ # under ThreadSanitizer). Idempotent; runs whether or not sources were re-fetched.
62
+ sweodef = File.join(__dir__, 'sweodef.h')
63
+ if File.exist?(sweodef)
64
+ src = File.read(sweodef)
65
+ patched = src.sub('!defined(TLSOFF) && !defined( __APPLE__ )', '!defined(TLSOFF)')
66
+ if patched != src
67
+ File.write(sweodef, patched)
68
+ puts 'Patched sweodef.h to enable thread-local swed on Apple.'
69
+ end
70
+ end
71
+
57
72
  # Get all C source files, excluding utility programs
58
73
  $srcs = Dir.glob('*.c').select { |f|
59
74
  !['swephgen4.c', 'swemini.c', 'sweasp.c', 'swevents.c', 'swetest.c', 'sweephe4.c'].include?(f)
@@ -21,6 +21,7 @@ along with Swisseph. If not, see <http://www.gnu.org/licenses/>.
21
21
 
22
22
  // https://docs.ruby-lang.org/en/3.0/extension_rdoc.html
23
23
  #include <ruby.h>
24
+ #include <string.h>
24
25
  #include "swephexp.h"
25
26
 
26
27
  // Module Name
@@ -1660,6 +1661,280 @@ static VALUE t_swe_lun_eclipse_how(VALUE self, VALUE tjd, VALUE ifl, VALUE lon,
1660
1661
  return output;
1661
1662
  }
1662
1663
 
1664
+ /*
1665
+ * Extended atmospheric refraction, with geographic altitude and lapse rate.
1666
+ * double swe_refrac_extended(double inalt, double geoalt, double atpress, double attemp,
1667
+ * double lapse_rate, int32 calc_flag, double *dret);
1668
+ * calc_flag: SE_TRUE_TO_APP or SE_APP_TO_TRUE
1669
+ * Returns [result, dret[4]] where dret is
1670
+ * [0] true altitude (if possible), [1] apparent altitude,
1671
+ * [2] refraction, [3] dip of horizon
1672
+ */
1673
+ static VALUE t_swe_refrac_extended(VALUE self, VALUE inalt, VALUE geoalt, VALUE atpress, VALUE attemp, VALUE lapse_rate, VALUE calc_flag)
1674
+ {
1675
+ double dret[20];
1676
+ double result = swe_refrac_extended(NUM2DBL(inalt), NUM2DBL(geoalt), NUM2DBL(atpress), NUM2DBL(attemp), NUM2DBL(lapse_rate), NUM2INT(calc_flag), dret);
1677
+
1678
+ VALUE output = rb_ary_new();
1679
+ rb_ary_push(output, rb_float_new(result));
1680
+ for (int i = 0; i < 4; i++)
1681
+ rb_ary_push(output, rb_float_new(dret[i]));
1682
+ return output;
1683
+ }
1684
+
1685
+ /*
1686
+ * Phenomena relevant to heliacal (and other) visibility events.
1687
+ * int32 swe_heliacal_pheno_ut(double tjd_ut, double *geopos, double *datm, double *dobs,
1688
+ * char *ObjectName, int32 TypeEvent, int32 helflag, double *darr, char *serr);
1689
+ * Args: (tjd_ut, object_name, type_event, helflag, lon, lat, height, datm_array, dobs_array)
1690
+ * Returns darr[50]
1691
+ */
1692
+ static VALUE t_swe_heliacal_pheno_ut(int argc, VALUE *argv, VALUE self)
1693
+ {
1694
+ if (argc != 9)
1695
+ rb_raise(rb_eArgError, "wrong number of arguments (9 required: tjd_ut, object_name, type_event, helflag, lon, lat, height, datm_array, dobs_array)");
1696
+
1697
+ char serr[AS_MAXCH];
1698
+ double geopos[3];
1699
+ geopos[0] = NUM2DBL(argv[4]); // lon
1700
+ geopos[1] = NUM2DBL(argv[5]); // lat
1701
+ geopos[2] = NUM2DBL(argv[6]); // height
1702
+
1703
+ VALUE datm_arr = argv[7];
1704
+ if (TYPE(datm_arr) != T_ARRAY || RARRAY_LEN(datm_arr) < 4)
1705
+ rb_raise(rb_eArgError, "datm must be array of 4 values [pressure, temp, humidity, extinction_coeff]");
1706
+ double datm[4];
1707
+ for (int i = 0; i < 4; i++)
1708
+ datm[i] = NUM2DBL(rb_ary_entry(datm_arr, i));
1709
+
1710
+ VALUE dobs_arr = argv[8];
1711
+ if (TYPE(dobs_arr) != T_ARRAY || RARRAY_LEN(dobs_arr) < 6)
1712
+ rb_raise(rb_eArgError, "dobs must be array of 6 values [age, snellen_left, snellen_right, telescope_mag, telescope_diam, binoc_factor]");
1713
+ double dobs[6];
1714
+ for (int i = 0; i < 6; i++)
1715
+ dobs[i] = NUM2DBL(rb_ary_entry(dobs_arr, i));
1716
+
1717
+ double darr[50];
1718
+ char *object_name = StringValuePtr(argv[1]);
1719
+
1720
+ if (swe_heliacal_pheno_ut(NUM2DBL(argv[0]), geopos, datm, dobs, object_name, NUM2INT(argv[2]), NUM2INT(argv[3]), darr, serr) < 0)
1721
+ rb_raise(rb_eRuntimeError, "%s", serr);
1722
+
1723
+ VALUE output = rb_ary_new();
1724
+ for (int i = 0; i < 50; i++)
1725
+ rb_ary_push(output, rb_float_new(darr[i]));
1726
+ return output;
1727
+ }
1728
+
1729
+ /*
1730
+ * Compute the angle (arcus visionis, and object/sun altitudes) at which an object
1731
+ * of given magnitude becomes heliacally visible.
1732
+ * int32 swe_heliacal_angle(double tjdut, double *dgeo, double *datm, double *dobs, int32 helflag,
1733
+ * double mag, double azi_obj, double azi_sun, double azi_moon, double alt_moon, double *dret, char *serr);
1734
+ * Args: (tjdut, helflag, mag, azi_obj, azi_sun, azi_moon, alt_moon, lon, lat, height, datm_array, dobs_array)
1735
+ * Returns dret[3]: [0] topocentric arcus visionis, [1] object altitude, [2] sun altitude
1736
+ */
1737
+ static VALUE t_swe_heliacal_angle(int argc, VALUE *argv, VALUE self)
1738
+ {
1739
+ if (argc != 12)
1740
+ rb_raise(rb_eArgError, "wrong number of arguments (12 required: tjdut, helflag, mag, azi_obj, azi_sun, azi_moon, alt_moon, lon, lat, height, datm_array, dobs_array)");
1741
+
1742
+ char serr[AS_MAXCH];
1743
+ double dgeo[3];
1744
+ dgeo[0] = NUM2DBL(argv[7]); // lon
1745
+ dgeo[1] = NUM2DBL(argv[8]); // lat
1746
+ dgeo[2] = NUM2DBL(argv[9]); // height
1747
+
1748
+ VALUE datm_arr = argv[10];
1749
+ if (TYPE(datm_arr) != T_ARRAY || RARRAY_LEN(datm_arr) < 4)
1750
+ rb_raise(rb_eArgError, "datm must be array of 4 values [pressure, temp, humidity, extinction_coeff]");
1751
+ double datm[4];
1752
+ for (int i = 0; i < 4; i++)
1753
+ datm[i] = NUM2DBL(rb_ary_entry(datm_arr, i));
1754
+
1755
+ VALUE dobs_arr = argv[11];
1756
+ if (TYPE(dobs_arr) != T_ARRAY || RARRAY_LEN(dobs_arr) < 6)
1757
+ rb_raise(rb_eArgError, "dobs must be array of 6 values [age, snellen_left, snellen_right, telescope_mag, telescope_diam, binoc_factor]");
1758
+ double dobs[6];
1759
+ for (int i = 0; i < 6; i++)
1760
+ dobs[i] = NUM2DBL(rb_ary_entry(dobs_arr, i));
1761
+
1762
+ double dret[20];
1763
+ if (swe_heliacal_angle(NUM2DBL(argv[0]), dgeo, datm, dobs, NUM2INT(argv[1]),
1764
+ NUM2DBL(argv[2]), NUM2DBL(argv[3]), NUM2DBL(argv[4]), NUM2DBL(argv[5]), NUM2DBL(argv[6]), dret, serr) < 0)
1765
+ rb_raise(rb_eRuntimeError, "%s", serr);
1766
+
1767
+ VALUE output = rb_ary_new();
1768
+ for (int i = 0; i < 3; i++)
1769
+ rb_ary_push(output, rb_float_new(dret[i]));
1770
+ return output;
1771
+ }
1772
+
1773
+ /*
1774
+ * Compute the topocentric arcus visionis for an object of given magnitude and geometry.
1775
+ * int32 swe_topo_arcus_visionis(double tjdut, double *dgeo, double *datm, double *dobs, int32 helflag,
1776
+ * double mag, double azi_obj, double alt_obj, double azi_sun, double azi_moon, double alt_moon, double *dret, char *serr);
1777
+ * Args: (tjdut, helflag, mag, azi_obj, alt_obj, azi_sun, azi_moon, alt_moon, lon, lat, height, datm_array, dobs_array)
1778
+ * Returns dret[1]: topocentric arcus visionis in degrees
1779
+ */
1780
+ static VALUE t_swe_topo_arcus_visionis(int argc, VALUE *argv, VALUE self)
1781
+ {
1782
+ if (argc != 13)
1783
+ rb_raise(rb_eArgError, "wrong number of arguments (13 required: tjdut, helflag, mag, azi_obj, alt_obj, azi_sun, azi_moon, alt_moon, lon, lat, height, datm_array, dobs_array)");
1784
+
1785
+ char serr[AS_MAXCH];
1786
+ double dgeo[3];
1787
+ dgeo[0] = NUM2DBL(argv[8]); // lon
1788
+ dgeo[1] = NUM2DBL(argv[9]); // lat
1789
+ dgeo[2] = NUM2DBL(argv[10]); // height
1790
+
1791
+ VALUE datm_arr = argv[11];
1792
+ if (TYPE(datm_arr) != T_ARRAY || RARRAY_LEN(datm_arr) < 4)
1793
+ rb_raise(rb_eArgError, "datm must be array of 4 values [pressure, temp, humidity, extinction_coeff]");
1794
+ double datm[4];
1795
+ for (int i = 0; i < 4; i++)
1796
+ datm[i] = NUM2DBL(rb_ary_entry(datm_arr, i));
1797
+
1798
+ VALUE dobs_arr = argv[12];
1799
+ if (TYPE(dobs_arr) != T_ARRAY || RARRAY_LEN(dobs_arr) < 6)
1800
+ rb_raise(rb_eArgError, "dobs must be array of 6 values [age, snellen_left, snellen_right, telescope_mag, telescope_diam, binoc_factor]");
1801
+ double dobs[6];
1802
+ for (int i = 0; i < 6; i++)
1803
+ dobs[i] = NUM2DBL(rb_ary_entry(dobs_arr, i));
1804
+
1805
+ double dret[20];
1806
+ if (swe_topo_arcus_visionis(NUM2DBL(argv[0]), dgeo, datm, dobs, NUM2INT(argv[1]),
1807
+ NUM2DBL(argv[2]), NUM2DBL(argv[3]), NUM2DBL(argv[4]), NUM2DBL(argv[5]), NUM2DBL(argv[6]), NUM2DBL(argv[7]), dret, serr) < 0)
1808
+ rb_raise(rb_eRuntimeError, "%s", serr);
1809
+
1810
+ VALUE output = rb_ary_new();
1811
+ rb_ary_push(output, rb_float_new(dret[0]));
1812
+ return output;
1813
+ }
1814
+
1815
+ /*
1816
+ * Find geographic location where an occultation of a body by the Moon is
1817
+ * central (or maximal) at a given tjd. This is the occultation analogue of
1818
+ * swe_sol_eclipse_where (a solar eclipse is the Moon occulting the Sun).
1819
+ * int32 swe_lun_occult_where(double tjd, int32 ipl, char *starname, int32 ifl, double *geopos, double *attr, char *serr);
1820
+ * ipl: planet number of the occulted body (ignored when starname is given)
1821
+ * starname: fixed-star name, or nil / "" to occult the planet in ipl
1822
+ * Returns [type, geopos[2], attr[20]]
1823
+ */
1824
+ static VALUE t_swe_lun_occult_where(VALUE self, VALUE tjd, VALUE ipl, VALUE starname, VALUE ifl)
1825
+ {
1826
+ char serr[AS_MAXCH];
1827
+ char star[AS_MAXCH];
1828
+ double geopos[10];
1829
+ double attr[20];
1830
+
1831
+ if (NIL_P(starname))
1832
+ star[0] = '\0';
1833
+ else {
1834
+ strncpy(star, StringValueCStr(starname), AS_MAXCH - 1);
1835
+ star[AS_MAXCH - 1] = '\0';
1836
+ }
1837
+
1838
+ int32 result = swe_lun_occult_where(NUM2DBL(tjd), NUM2INT(ipl), star, NUM2INT(ifl), geopos, attr, serr);
1839
+ if (result < 0)
1840
+ rb_raise(rb_eRuntimeError, "%s", serr);
1841
+
1842
+ VALUE output = rb_ary_new();
1843
+ rb_ary_push(output, INT2NUM(result)); // eclipse type flags
1844
+
1845
+ VALUE _geopos = rb_ary_new();
1846
+ rb_ary_push(_geopos, rb_float_new(geopos[0])); // longitude
1847
+ rb_ary_push(_geopos, rb_float_new(geopos[1])); // latitude
1848
+ rb_ary_push(output, _geopos);
1849
+
1850
+ VALUE _attr = rb_ary_new();
1851
+ for (int i = 0; i < 20; i++)
1852
+ rb_ary_push(_attr, rb_float_new(attr[i]));
1853
+ rb_ary_push(output, _attr);
1854
+
1855
+ return output;
1856
+ }
1857
+
1858
+ /*
1859
+ * Find the time of the next (or previous) occultation of a body by the Moon
1860
+ * visible from a given location. Occultation analogue of swe_sol_eclipse_when_loc.
1861
+ * int32 swe_lun_occult_when_loc(double tjd_start, int32 ipl, char *starname, int32 ifl,
1862
+ * double *geopos, double *tret, double *attr, int32 backward, char *serr);
1863
+ * starname: fixed-star name, or nil / "" to occult the planet in ipl
1864
+ * Returns [type, tret[10], attr[20]]
1865
+ */
1866
+ static VALUE t_swe_lun_occult_when_loc(VALUE self, VALUE tjd_start, VALUE ipl, VALUE starname, VALUE ifl, VALUE lon, VALUE lat, VALUE height, VALUE backward)
1867
+ {
1868
+ char serr[AS_MAXCH];
1869
+ char star[AS_MAXCH];
1870
+ double geopos[3];
1871
+ double tret[10];
1872
+ double attr[20];
1873
+
1874
+ geopos[0] = NUM2DBL(lon);
1875
+ geopos[1] = NUM2DBL(lat);
1876
+ geopos[2] = NUM2DBL(height);
1877
+
1878
+ if (NIL_P(starname))
1879
+ star[0] = '\0';
1880
+ else {
1881
+ strncpy(star, StringValueCStr(starname), AS_MAXCH - 1);
1882
+ star[AS_MAXCH - 1] = '\0';
1883
+ }
1884
+
1885
+ int32 result = swe_lun_occult_when_loc(NUM2DBL(tjd_start), NUM2INT(ipl), star, NUM2INT(ifl), geopos, tret, attr, NUM2INT(backward), serr);
1886
+ if (result < 0)
1887
+ rb_raise(rb_eRuntimeError, "%s", serr);
1888
+
1889
+ VALUE output = rb_ary_new();
1890
+ rb_ary_push(output, INT2NUM(result)); // eclipse type flags
1891
+
1892
+ VALUE _tret = rb_ary_new();
1893
+ for (int i = 0; i < 10; i++)
1894
+ rb_ary_push(_tret, rb_float_new(tret[i]));
1895
+ rb_ary_push(output, _tret);
1896
+
1897
+ VALUE _attr = rb_ary_new();
1898
+ for (int i = 0; i < 20; i++)
1899
+ rb_ary_push(_attr, rb_float_new(attr[i]));
1900
+ rb_ary_push(output, _attr);
1901
+
1902
+ return output;
1903
+ }
1904
+
1905
+ /*
1906
+ * Find the time of the next (or previous) occultation of a body by the Moon
1907
+ * anywhere on Earth. Occultation analogue of swe_sol_eclipse_when_glob.
1908
+ * int32 swe_lun_occult_when_glob(double tjd_start, int32 ipl, char *starname, int32 ifl, int32 ifltype,
1909
+ * double *tret, int32 backward, char *serr);
1910
+ * ifltype: SE_ECL_TOTAL, SE_ECL_ANNULAR, SE_ECL_PARTIAL, SE_ECL_CENTRAL, SE_ECL_NONCENTRAL, or 0 for any
1911
+ * starname: fixed-star name, or nil / "" to occult the planet in ipl
1912
+ * Returns [type, tret[10]]
1913
+ */
1914
+ static VALUE t_swe_lun_occult_when_glob(VALUE self, VALUE tjd_start, VALUE ipl, VALUE starname, VALUE ifl, VALUE ifltype, VALUE backward)
1915
+ {
1916
+ char serr[AS_MAXCH];
1917
+ char star[AS_MAXCH];
1918
+ double tret[10];
1919
+
1920
+ if (NIL_P(starname))
1921
+ star[0] = '\0';
1922
+ else {
1923
+ strncpy(star, StringValueCStr(starname), AS_MAXCH - 1);
1924
+ star[AS_MAXCH - 1] = '\0';
1925
+ }
1926
+
1927
+ int32 result = swe_lun_occult_when_glob(NUM2DBL(tjd_start), NUM2INT(ipl), star, NUM2INT(ifl), NUM2INT(ifltype), tret, NUM2INT(backward), serr);
1928
+ if (result < 0)
1929
+ rb_raise(rb_eRuntimeError, "%s", serr);
1930
+
1931
+ VALUE output = rb_ary_new();
1932
+ rb_ary_push(output, INT2NUM(result)); // eclipse type flags
1933
+ for (int i = 0; i < 10; i++)
1934
+ rb_ary_push(output, rb_float_new(tret[i]));
1935
+ return output;
1936
+ }
1937
+
1663
1938
  void Init_swisseph()
1664
1939
  {
1665
1940
  // Module
@@ -1698,6 +1973,7 @@ void Init_swisseph()
1698
1973
  rb_define_module_function(rb_mSwisseph, "swe_azalt", t_swe_azalt, 10);
1699
1974
  rb_define_module_function(rb_mSwisseph, "swe_azalt_rev", t_swe_azalt_rev, 7);
1700
1975
  rb_define_module_function(rb_mSwisseph, "swe_refrac", t_swe_refrac, 4);
1976
+ rb_define_module_function(rb_mSwisseph, "swe_refrac_extended", t_swe_refrac_extended, 6);
1701
1977
  rb_define_module_function(rb_mSwisseph, "swe_pheno_ut", t_swe_pheno_ut, 3);
1702
1978
  rb_define_module_function(rb_mSwisseph, "swe_time_equ", t_swe_time_equ, 1);
1703
1979
  rb_define_module_function(rb_mSwisseph, "swe_lmt_to_lat", t_swe_lmt_to_lat, 2);
@@ -1726,9 +2002,15 @@ void Init_swisseph()
1726
2002
  rb_define_module_function(rb_mSwisseph, "swe_lun_eclipse_when", t_swe_lun_eclipse_when, 4);
1727
2003
  rb_define_module_function(rb_mSwisseph, "swe_lun_eclipse_when_loc", t_swe_lun_eclipse_when_loc, 6);
1728
2004
  rb_define_module_function(rb_mSwisseph, "swe_lun_eclipse_how", t_swe_lun_eclipse_how, 5);
2005
+ rb_define_module_function(rb_mSwisseph, "swe_lun_occult_where", t_swe_lun_occult_where, 4);
2006
+ rb_define_module_function(rb_mSwisseph, "swe_lun_occult_when_loc", t_swe_lun_occult_when_loc, 8);
2007
+ rb_define_module_function(rb_mSwisseph, "swe_lun_occult_when_glob", t_swe_lun_occult_when_glob, 6);
1729
2008
  rb_define_module_function(rb_mSwisseph, "swe_gauquelin_sector", t_swe_gauquelin_sector, 9);
1730
2009
  rb_define_module_function(rb_mSwisseph, "swe_heliacal_ut", t_swe_heliacal_ut, -1);
1731
2010
  rb_define_module_function(rb_mSwisseph, "swe_vis_limit_mag", t_swe_vis_limit_mag, -1);
2011
+ rb_define_module_function(rb_mSwisseph, "swe_heliacal_pheno_ut", t_swe_heliacal_pheno_ut, -1);
2012
+ rb_define_module_function(rb_mSwisseph, "swe_heliacal_angle", t_swe_heliacal_angle, -1);
2013
+ rb_define_module_function(rb_mSwisseph, "swe_topo_arcus_visionis", t_swe_topo_arcus_visionis, -1);
1732
2014
 
1733
2015
  // ET (Ephemeris Time) versions
1734
2016
  rb_define_module_function(rb_mSwisseph, "swe_calc", t_swe_calc, 3);
data/lib/swisseph.rb CHANGED
@@ -17,26 +17,39 @@ module Swisseph
17
17
  end
18
18
 
19
19
  module Swisseph
20
- # The Swiss Ephemeris C library (libswe) keeps process-global state and a
21
- # shared file descriptor for its data files, so it is NOT thread-safe: two
22
- # threads executing inside it at once can segfault the process. Every native
23
- # call is serialized through one global re-entrant lock so concurrent threads
24
- # -- e.g. a multi-threaded Puma app plus in-process background workers --
25
- # can never be inside libswe simultaneously. A single-threaded caller never
26
- # contends for the lock; concurrent callers are serialized (a brief wait, not
27
- # a segfault).
20
+ # The Swiss Ephemeris C library keeps its mutable state in one `struct swe_data
21
+ # swed`. On a build where `swed` is a plain process global it is NOT thread-safe:
22
+ # two threads inside the C library at once can segfault the process, so every
23
+ # native call is serialized through this global re-entrant lock.
24
+ #
25
+ # BUT libswe declares `swed` with the `TLS` macro (sweodef.h). On any build where
26
+ # TLS resolves to real thread-local storage (`__thread` / `_Thread_local` /
27
+ # `__declspec(thread)`) each thread gets its own `swed` -- own file descriptors,
28
+ # own caches -- and the library is genuinely per-thread reentrant. That is the
29
+ # case on Linux, and on macOS with the extconf.rb patch that drops the legacy
30
+ # `__APPLE__` exclusion (modern clang supports __thread). On such a build the
31
+ # lock is pure overhead: it serializes ephemeris work that could run in parallel.
28
32
  LOCK = Monitor.new
33
+
34
+ # Serialize native calls through LOCK unless explicitly disabled. Set
35
+ # SWISSEPH_NOLOCK=1 ONLY on a thread-local (TLS) build -- disabling the lock on a
36
+ # non-TLS build will segfault under concurrency. Reentrancy verified 2026-08-19
37
+ # under ThreadSanitizer (12 threads, no lock, zero data races, exact values).
38
+ LOCKING = ENV['SWISSEPH_NOLOCK'] != '1'
29
39
  end
30
40
 
31
- # Wrap every current singleton method (the native swe_* methods and the
32
- # shorthand aliases defined above) so each public entry point takes the lock.
33
- Swisseph.singleton_class.prepend(
34
- Module.new do
35
- lock = Swisseph::LOCK
36
- Swisseph.singleton_methods(false).each do |name|
37
- define_method(name) do |*args, &block|
38
- lock.synchronize { super(*args, &block) }
41
+ # Wrap every current singleton method (the native swe_* methods and the shorthand
42
+ # aliases defined above) so each public entry point takes the lock -- unless
43
+ # locking is disabled, in which case the native methods are called directly.
44
+ if Swisseph::LOCKING
45
+ Swisseph.singleton_class.prepend(
46
+ Module.new do
47
+ lock = Swisseph::LOCK
48
+ Swisseph.singleton_methods(false).each do |name|
49
+ define_method(name) do |*args, &block|
50
+ lock.synchronize { super(*args, &block) }
51
+ end
39
52
  end
40
53
  end
41
- end
42
- )
54
+ )
55
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swisseph
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Lowenfels